diff options
Diffstat (limited to 'vendor/go.mau.fi/libsignal/ratchet/Ratchet.go')
-rw-r--r-- | vendor/go.mau.fi/libsignal/ratchet/Ratchet.go | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/vendor/go.mau.fi/libsignal/ratchet/Ratchet.go b/vendor/go.mau.fi/libsignal/ratchet/Ratchet.go new file mode 100644 index 00000000..df25beb3 --- /dev/null +++ b/vendor/go.mau.fi/libsignal/ratchet/Ratchet.go @@ -0,0 +1,197 @@ +// 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 +} |