198 lines
7.1 KiB
Go
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
|
||
|
}
|