matterbridge/vendor/go.mau.fi/libsignal/ratchet/Ratchet.go
2022-03-20 14:57:48 +01:00

198 lines
7.1 KiB
Go

// Package ratchet provides the methods necessary to establish a new double
// ratchet session.
package ratchet
import (
"encoding/base64"
"encoding/binary"
"go.mau.fi/libsignal/ecc"
"go.mau.fi/libsignal/kdf"
"go.mau.fi/libsignal/keys/chain"
"go.mau.fi/libsignal/keys/root"
"go.mau.fi/libsignal/keys/session"
)
var b64 = base64.StdEncoding.EncodeToString
func genDiscontinuity() [32]byte {
var discontinuity [32]byte
for i := range discontinuity {
discontinuity[i] = 0xFF
}
return discontinuity
}
// CalculateSenderSession calculates the key agreement for a recipient. This
// should be used when we are trying to send a message to someone for the
// first time.
func CalculateSenderSession(parameters *SenderParameters) (*session.KeyPair, error) {
var secret [32]byte
var publicKey [32]byte
var privateKey [32]byte
masterSecret := []byte{} // Create a master shared secret that is 5 different 32-byte values
discontinuity := genDiscontinuity()
masterSecret = append(masterSecret, discontinuity[:]...)
// Calculate the agreement using their signed prekey and our identity key.
publicKey = parameters.TheirSignedPreKey().PublicKey()
privateKey = parameters.OurIdentityKey().PrivateKey().Serialize()
secret = kdf.CalculateSharedSecret(
publicKey,
privateKey,
)
masterSecret = append(masterSecret, secret[:]...)
// Calculate the agreement using their identity key and our base key.
publicKey = parameters.TheirIdentityKey().PublicKey().PublicKey()
privateKey = parameters.OurBaseKey().PrivateKey().Serialize()
secret = kdf.CalculateSharedSecret(
publicKey,
privateKey,
)
masterSecret = append(masterSecret, secret[:]...)
// Calculate the agreement using their signed prekey and our base key.
publicKey = parameters.TheirSignedPreKey().PublicKey()
privateKey = parameters.OurBaseKey().PrivateKey().Serialize()
secret = kdf.CalculateSharedSecret(
publicKey,
privateKey,
)
masterSecret = append(masterSecret, secret[:]...)
// If they have a one-time prekey, use it to calculate the shared secret with their
// one time key and our base key.
if parameters.TheirOneTimePreKey() != nil {
publicKey = parameters.TheirOneTimePreKey().PublicKey()
privateKey = parameters.OurBaseKey().PrivateKey().Serialize()
secret = kdf.CalculateSharedSecret(
publicKey,
privateKey,
)
masterSecret = append(masterSecret, secret[:]...)
}
// Derive the root and chain keys based on the master secret.
derivedKeysBytes, err := kdf.DeriveSecrets(masterSecret, nil, []byte("WhisperText"), root.DerivedSecretsSize)
if err != nil {
return nil, err
}
derivedKeys := session.NewDerivedSecrets(derivedKeysBytes)
chainKey := chain.NewKey(kdf.DeriveSecrets, derivedKeys.ChainKey(), 0)
rootKey := root.NewKey(kdf.DeriveSecrets, derivedKeys.RootKey())
// Add the root and chain keys to a structure that will hold both keys.
sessionKeys := session.NewKeyPair(rootKey, chainKey)
return sessionKeys, nil
}
// CalculateReceiverSession calculates the key agreement for a sender. This should
// be used when we are receiving a message from someone for the first time.
func CalculateReceiverSession(parameters *ReceiverParameters) (*session.KeyPair, error) {
var secret [32]byte
var publicKey [32]byte
var privateKey [32]byte
masterSecret := []byte{} // Create a master shared secret that is 5 different 32-byte values
discontinuity := genDiscontinuity()
masterSecret = append(masterSecret, discontinuity[:]...)
// Calculate the agreement using their identity key and our signed pre key.
publicKey = parameters.TheirIdentityKey().PublicKey().PublicKey()
privateKey = parameters.OurSignedPreKey().PrivateKey().Serialize()
secret = kdf.CalculateSharedSecret(
publicKey,
privateKey,
)
masterSecret = append(masterSecret, secret[:]...)
// Calculate the agreement using their base key and our identity key.
publicKey = parameters.TheirBaseKey().PublicKey()
privateKey = parameters.OurIdentityKeyPair().PrivateKey().Serialize()
secret = kdf.CalculateSharedSecret(
publicKey,
privateKey,
)
masterSecret = append(masterSecret, secret[:]...)
// Calculate the agreement using their base key and our signed prekey.
publicKey = parameters.TheirBaseKey().PublicKey()
privateKey = parameters.OurSignedPreKey().PrivateKey().Serialize()
secret = kdf.CalculateSharedSecret(
publicKey,
privateKey,
)
masterSecret = append(masterSecret, secret[:]...)
// If we had a one-time prekey, use it to calculate the shared secret with our
// one time key and their base key.
if parameters.OurOneTimePreKey() != nil {
publicKey = parameters.TheirBaseKey().PublicKey()
privateKey = parameters.OurOneTimePreKey().PrivateKey().Serialize()
secret = kdf.CalculateSharedSecret(
publicKey,
privateKey,
)
masterSecret = append(masterSecret, secret[:]...)
}
// Derive the root and chain keys based on the master secret.
derivedKeysBytes, err := kdf.DeriveSecrets(masterSecret, nil, []byte("WhisperText"), root.DerivedSecretsSize)
if err != nil {
return nil, err
}
derivedKeys := session.NewDerivedSecrets(derivedKeysBytes)
chainKey := chain.NewKey(kdf.DeriveSecrets, derivedKeys.ChainKey(), 0)
rootKey := root.NewKey(kdf.DeriveSecrets, derivedKeys.RootKey())
// Add the root and chain keys to a structure that will hold both keys.
sessionKeys := session.NewKeyPair(rootKey, chainKey)
return sessionKeys, nil
}
// CalculateSymmetricSession calculates the key agreement between two users. This
// works by both clients exchanging KeyExchange messages to first establish a session.
// This is useful for establishing a session if both users are online.
func CalculateSymmetricSession(parameters *SymmetricParameters) (*session.KeyPair, error) {
// Compare the base public keys so we can deterministically know whether we should
// be setting up a sender or receiver session. If our key converted to an integer is
// less than the other user's, act as a sender.
if isSender(parameters.OurBaseKey.PublicKey(), parameters.TheirBaseKey) {
senderParameters := &SenderParameters{
ourBaseKey: parameters.OurBaseKey,
ourIdentityKeyPair: parameters.OurIdentityKeyPair,
theirRatchetKey: parameters.TheirRatchetKey,
theirIdentityKey: parameters.TheirIdentityKey,
theirSignedPreKey: parameters.TheirBaseKey,
}
return CalculateSenderSession(senderParameters)
}
// If our base public key was larger than the other user's, act as a receiver.
receiverParameters := &ReceiverParameters{
ourIdentityKeyPair: parameters.OurIdentityKeyPair,
ourRatchetKey: parameters.OurRatchetKey,
ourSignedPreKey: parameters.OurBaseKey,
theirBaseKey: parameters.TheirBaseKey,
theirIdentityKey: parameters.TheirIdentityKey,
}
return CalculateReceiverSession(receiverParameters)
}
// isSender is a private method for determining if a symmetric session should
// be calculated as the sender or receiver. It does so by converting the given
// keys into integers and comparing the size of those integers.
func isSender(ourKey, theirKey ecc.ECPublicKeyable) bool {
ourKeyInt := binary.BigEndian.Uint32(ourKey.Serialize())
theirKeyInt := binary.BigEndian.Uint32(theirKey.Serialize())
return ourKeyInt < theirKeyInt
}