forked from jshiffer/matterbridge
128 lines
3.5 KiB
Go
128 lines
3.5 KiB
Go
// Package chain provides chain keys used in double ratchet sessions.
|
|
package chain
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"go.mau.fi/libsignal/kdf"
|
|
"go.mau.fi/libsignal/keys/message"
|
|
)
|
|
|
|
var messageKeySeed = []byte{0x01}
|
|
var chainKeySeed = []byte{0x02}
|
|
|
|
// NewKey returns a new chain key with the given kdf, key, and index
|
|
func NewKey(kdf kdf.HKDF, key []byte, index uint32) *Key {
|
|
chainKey := Key{
|
|
kdf: kdf,
|
|
key: key,
|
|
index: index,
|
|
}
|
|
|
|
return &chainKey
|
|
}
|
|
|
|
// NewKeyFromStruct will return a chain key built from the given structure.
|
|
func NewKeyFromStruct(structure *KeyStructure, kdf kdf.HKDF) *Key {
|
|
return NewKey(
|
|
kdf,
|
|
structure.Key,
|
|
structure.Index,
|
|
)
|
|
}
|
|
|
|
// NewStructFromKey will return a chain key structure for serialization.
|
|
func NewStructFromKey(key *Key) *KeyStructure {
|
|
return &KeyStructure{
|
|
Key: key.key,
|
|
Index: key.index,
|
|
}
|
|
}
|
|
|
|
// KeyStructure is a serializeable structure for chain keys.
|
|
type KeyStructure struct {
|
|
Key []byte
|
|
Index uint32
|
|
}
|
|
|
|
// Key is used for generating message keys. This key "ratchets" every time a
|
|
// message key is generated. Every time the chain key ratchets forward, its index
|
|
// increases by one.
|
|
type Key struct {
|
|
kdf kdf.HKDF
|
|
key []byte
|
|
index uint32 // Index's maximum size: 4,294,967,295
|
|
}
|
|
|
|
// Current returns the current ChainKey struct.
|
|
func (c *Key) Current() *Key {
|
|
return c
|
|
}
|
|
|
|
// Key returns the ChainKey's key material.
|
|
func (c *Key) Key() []byte {
|
|
return c.key
|
|
}
|
|
|
|
// SetKey will set the ChainKey's key material.
|
|
func (c *Key) SetKey(key []byte) {
|
|
c.key = key
|
|
}
|
|
|
|
// Index returns how many times the ChainKey has been "ratcheted" forward.
|
|
func (c *Key) Index() uint32 {
|
|
return c.index
|
|
}
|
|
|
|
// SetIndex sets how many times the ChainKey has been "ratcheted" forward.
|
|
func (c *Key) SetIndex(index uint32) {
|
|
c.index = index
|
|
}
|
|
|
|
// NextKey uses the key derivation function to generate a new ChainKey.
|
|
func (c *Key) NextKey() *Key {
|
|
nextKey := c.BaseMaterial(chainKeySeed)
|
|
return NewKey(c.kdf, nextKey, c.index+1)
|
|
}
|
|
|
|
// MessageKeys returns message keys, which includes the cipherkey, mac, iv, and index.
|
|
func (c *Key) MessageKeys() *message.Keys {
|
|
inputKeyMaterial := c.BaseMaterial(messageKeySeed)
|
|
keyMaterialBytes, _ := c.kdf(inputKeyMaterial, nil, []byte(message.KdfSalt), message.DerivedSecretsSize)
|
|
keyMaterial := newKeyMaterial(keyMaterialBytes)
|
|
|
|
// Use the key material returned from the key derivation function for our cipherkey, mac, and iv.
|
|
messageKeys := message.NewKeys(
|
|
keyMaterial.CipherKey, // Use the first 32 bytes of the key material for the CipherKey
|
|
keyMaterial.MacKey, // Use bytes 32-64 of the key material for the MacKey
|
|
keyMaterial.IV, // Use the last 16 bytes for the IV.
|
|
c.Index(), // Attach the chain key's index to the message keys.
|
|
)
|
|
|
|
return messageKeys
|
|
}
|
|
|
|
// BaseMaterial uses hmac to derive the base material used in the key derivation function for a new key.
|
|
func (c *Key) BaseMaterial(seed []byte) []byte {
|
|
mac := hmac.New(sha256.New, c.key[:])
|
|
mac.Write(seed)
|
|
|
|
return mac.Sum(nil)
|
|
}
|
|
|
|
// NewKeyMaterial takes an 80-byte slice derived from a key derivation function and splits
|
|
// it into the cipherkey, mac, and iv.
|
|
func newKeyMaterial(keyMaterialBytes []byte) *kdf.KeyMaterial {
|
|
cipherKey := keyMaterialBytes[:32] // Use the first 32 bytes of the key material for the CipherKey
|
|
macKey := keyMaterialBytes[32:64] // Use bytes 32-64 of the key material for the MacKey
|
|
iv := keyMaterialBytes[64:80] // Use the last 16 bytes for the IV.
|
|
|
|
keyMaterial := kdf.KeyMaterial{
|
|
CipherKey: cipherKey,
|
|
MacKey: macKey,
|
|
IV: iv,
|
|
}
|
|
|
|
return &keyMaterial
|
|
}
|