refactor to mautrix 0.17.x; update deps

This commit is contained in:
Aine
2024-02-11 20:47:04 +02:00
parent 0a9701f4c9
commit dd0ad4c245
237 changed files with 9091 additions and 3317 deletions

View File

@@ -0,0 +1,70 @@
package message
import (
"encoding/binary"
"maunium.net/go/mautrix/crypto/goolm"
)
// checkDecodeErr checks if there was an error during decode.
func checkDecodeErr(readBytes int) error {
if readBytes == 0 {
//end reached
return goolm.ErrInputToSmall
}
if readBytes < 0 {
return goolm.ErrOverflow
}
return nil
}
// decodeVarInt decodes a single big-endian encoded varint.
func decodeVarInt(input []byte) (uint32, int) {
value, readBytes := binary.Uvarint(input)
return uint32(value), readBytes
}
// decodeVarString decodes the length of the string (varint) and returns the actual string
func decodeVarString(input []byte) ([]byte, int) {
stringLen, readBytes := decodeVarInt(input)
if readBytes <= 0 {
return nil, readBytes
}
input = input[readBytes:]
value := input[:stringLen]
readBytes += int(stringLen)
return value, readBytes
}
// encodeVarIntByteLength returns the number of bytes needed to encode the uint32.
func encodeVarIntByteLength(input uint32) int {
result := 1
for input >= 128 {
result++
input >>= 7
}
return result
}
// encodeVarStringByteLength returns the number of bytes needed to encode the input.
func encodeVarStringByteLength(input []byte) int {
result := encodeVarIntByteLength(uint32(len(input)))
result += len(input)
return result
}
// encodeVarInt encodes a single uint32
func encodeVarInt(input uint32) []byte {
out := make([]byte, encodeVarIntByteLength(input))
binary.PutUvarint(out, uint64(input))
return out
}
// encodeVarString encodes the length of the input (varint) and appends the actual input
func encodeVarString(input []byte) []byte {
out := make([]byte, encodeVarStringByteLength(input))
length := encodeVarInt(uint32(len(input)))
copy(out, length)
copy(out[len(length):], input)
return out
}

View File

@@ -0,0 +1,144 @@
package message
import (
"bytes"
"maunium.net/go/mautrix/crypto/goolm/cipher"
"maunium.net/go/mautrix/crypto/goolm/crypto"
)
const (
messageIndexTag = 0x08
cipherTextTag = 0x12
countMACBytesGroupMessage = 8
)
// GroupMessage represents a message in the group message format.
type GroupMessage struct {
Version byte `json:"version"`
MessageIndex uint32 `json:"index"`
Ciphertext []byte `json:"ciphertext"`
HasMessageIndex bool `json:"has_index"`
}
// Decodes decodes the input and populates the corresponding fileds. MAC and signature are ignored but have to be present.
func (r *GroupMessage) Decode(input []byte) error {
r.Version = 0
r.MessageIndex = 0
r.Ciphertext = nil
if len(input) == 0 {
return nil
}
//first Byte is always version
r.Version = input[0]
curPos := 1
for curPos < len(input)-countMACBytesGroupMessage-crypto.ED25519SignatureSize {
//Read Key
curKey, readBytes := decodeVarInt(input[curPos:])
if err := checkDecodeErr(readBytes); err != nil {
return err
}
curPos += readBytes
if (curKey & 0b111) == 0 {
//The value is of type varint
value, readBytes := decodeVarInt(input[curPos:])
if err := checkDecodeErr(readBytes); err != nil {
return err
}
curPos += readBytes
switch curKey {
case messageIndexTag:
r.MessageIndex = value
r.HasMessageIndex = true
}
} else if (curKey & 0b111) == 2 {
//The value is of type string
value, readBytes := decodeVarString(input[curPos:])
if err := checkDecodeErr(readBytes); err != nil {
return err
}
curPos += readBytes
switch curKey {
case cipherTextTag:
r.Ciphertext = value
}
}
}
return nil
}
// EncodeAndMacAndSign encodes the message, creates the mac with the key and the cipher and signs the message.
// If macKey or cipher is nil, no mac is appended. If signKey is nil, no signature is appended.
func (r *GroupMessage) EncodeAndMacAndSign(macKey []byte, cipher cipher.Cipher, signKey *crypto.Ed25519KeyPair) ([]byte, error) {
var lengthOfMessage int
lengthOfMessage += 1 //Version
lengthOfMessage += encodeVarIntByteLength(messageIndexTag) + encodeVarIntByteLength(r.MessageIndex)
lengthOfMessage += encodeVarIntByteLength(cipherTextTag) + encodeVarStringByteLength(r.Ciphertext)
out := make([]byte, lengthOfMessage)
out[0] = r.Version
curPos := 1
encodedTag := encodeVarInt(messageIndexTag)
copy(out[curPos:], encodedTag)
curPos += len(encodedTag)
encodedValue := encodeVarInt(r.MessageIndex)
copy(out[curPos:], encodedValue)
curPos += len(encodedValue)
encodedTag = encodeVarInt(cipherTextTag)
copy(out[curPos:], encodedTag)
curPos += len(encodedTag)
encodedValue = encodeVarString(r.Ciphertext)
copy(out[curPos:], encodedValue)
curPos += len(encodedValue)
if len(macKey) != 0 && cipher != nil {
mac, err := r.MAC(macKey, cipher, out)
if err != nil {
return nil, err
}
out = append(out, mac[:countMACBytesGroupMessage]...)
}
if signKey != nil {
signature := signKey.Sign(out)
out = append(out, signature...)
}
return out, nil
}
// MAC returns the MAC of the message calculated with cipher and key. The length of the MAC is truncated to the correct length.
func (r *GroupMessage) MAC(key []byte, cipher cipher.Cipher, message []byte) ([]byte, error) {
mac, err := cipher.MAC(key, message)
if err != nil {
return nil, err
}
return mac[:countMACBytesGroupMessage], nil
}
// VerifySignature verifies the givenSignature to the calculated signature of the message.
func (r *GroupMessage) VerifySignature(key crypto.Ed25519PublicKey, message, givenSignature []byte) bool {
return key.Verify(message, givenSignature)
}
// VerifySignature verifies the signature taken from the message to the calculated signature of the message.
func (r *GroupMessage) VerifySignatureInline(key crypto.Ed25519PublicKey, message []byte) bool {
signature := message[len(message)-crypto.ED25519SignatureSize:]
message = message[:len(message)-crypto.ED25519SignatureSize]
return key.Verify(message, signature)
}
// VerifyMAC verifies the givenMAC to the calculated MAC of the message.
func (r *GroupMessage) VerifyMAC(key []byte, cipher cipher.Cipher, message, givenMAC []byte) (bool, error) {
checkMac, err := r.MAC(key, cipher, message)
if err != nil {
return false, err
}
return bytes.Equal(checkMac[:countMACBytesGroupMessage], givenMAC), nil
}
// VerifyMACInline verifies the MAC taken from the message to the calculated MAC of the message.
func (r *GroupMessage) VerifyMACInline(key []byte, cipher cipher.Cipher, message []byte) (bool, error) {
startMAC := len(message) - countMACBytesGroupMessage - crypto.ED25519SignatureSize
endMAC := startMAC + countMACBytesGroupMessage
suplMac := message[startMAC:endMAC]
message = message[:startMAC]
return r.VerifyMAC(key, cipher, message, suplMac)
}

View File

@@ -0,0 +1,129 @@
package message
import (
"bytes"
"maunium.net/go/mautrix/crypto/goolm/cipher"
"maunium.net/go/mautrix/crypto/goolm/crypto"
)
const (
ratchetKeyTag = 0x0A
counterTag = 0x10
cipherTextKeyTag = 0x22
countMACBytesMessage = 8
)
// GroupMessage represents a message in the message format.
type Message struct {
Version byte `json:"version"`
HasCounter bool `json:"has_counter"`
Counter uint32 `json:"counter"`
RatchetKey crypto.Curve25519PublicKey `json:"ratchet_key"`
Ciphertext []byte `json:"ciphertext"`
}
// Decodes decodes the input and populates the corresponding fileds. MAC is ignored but has to be present.
func (r *Message) Decode(input []byte) error {
r.Version = 0
r.HasCounter = false
r.Counter = 0
r.RatchetKey = nil
r.Ciphertext = nil
if len(input) == 0 {
return nil
}
//first Byte is always version
r.Version = input[0]
curPos := 1
for curPos < len(input)-countMACBytesMessage {
//Read Key
curKey, readBytes := decodeVarInt(input[curPos:])
if err := checkDecodeErr(readBytes); err != nil {
return err
}
curPos += readBytes
if (curKey & 0b111) == 0 {
//The value is of type varint
value, readBytes := decodeVarInt(input[curPos:])
if err := checkDecodeErr(readBytes); err != nil {
return err
}
curPos += readBytes
switch curKey {
case counterTag:
r.HasCounter = true
r.Counter = value
}
} else if (curKey & 0b111) == 2 {
//The value is of type string
value, readBytes := decodeVarString(input[curPos:])
if err := checkDecodeErr(readBytes); err != nil {
return err
}
curPos += readBytes
switch curKey {
case ratchetKeyTag:
r.RatchetKey = value
case cipherTextKeyTag:
r.Ciphertext = value
}
}
}
return nil
}
// EncodeAndMAC encodes the message and creates the MAC with the key and the cipher.
// If key or cipher is nil, no MAC is appended.
func (r *Message) EncodeAndMAC(key []byte, cipher cipher.Cipher) ([]byte, error) {
var lengthOfMessage int
lengthOfMessage += 1 //Version
lengthOfMessage += encodeVarIntByteLength(ratchetKeyTag) + encodeVarStringByteLength(r.RatchetKey)
lengthOfMessage += encodeVarIntByteLength(counterTag) + encodeVarIntByteLength(r.Counter)
lengthOfMessage += encodeVarIntByteLength(cipherTextKeyTag) + encodeVarStringByteLength(r.Ciphertext)
out := make([]byte, lengthOfMessage)
out[0] = r.Version
curPos := 1
encodedTag := encodeVarInt(ratchetKeyTag)
copy(out[curPos:], encodedTag)
curPos += len(encodedTag)
encodedValue := encodeVarString(r.RatchetKey)
copy(out[curPos:], encodedValue)
curPos += len(encodedValue)
encodedTag = encodeVarInt(counterTag)
copy(out[curPos:], encodedTag)
curPos += len(encodedTag)
encodedValue = encodeVarInt(r.Counter)
copy(out[curPos:], encodedValue)
curPos += len(encodedValue)
encodedTag = encodeVarInt(cipherTextKeyTag)
copy(out[curPos:], encodedTag)
curPos += len(encodedTag)
encodedValue = encodeVarString(r.Ciphertext)
copy(out[curPos:], encodedValue)
curPos += len(encodedValue)
if len(key) != 0 && cipher != nil {
mac, err := cipher.MAC(key, out)
if err != nil {
return nil, err
}
out = append(out, mac[:countMACBytesMessage]...)
}
return out, nil
}
// VerifyMAC verifies the givenMAC to the calculated MAC of the message.
func (r *Message) VerifyMAC(key []byte, cipher cipher.Cipher, message, givenMAC []byte) (bool, error) {
checkMAC, err := cipher.MAC(key, message)
if err != nil {
return false, err
}
return bytes.Equal(checkMAC[:countMACBytesMessage], givenMAC), nil
}
// VerifyMACInline verifies the MAC taken from the message to the calculated MAC of the message.
func (r *Message) VerifyMACInline(key []byte, cipher cipher.Cipher, message []byte) (bool, error) {
givenMAC := message[len(message)-countMACBytesMessage:]
return r.VerifyMAC(key, cipher, message[:len(message)-countMACBytesMessage], givenMAC)
}

View File

@@ -0,0 +1,120 @@
package message
import (
"maunium.net/go/mautrix/crypto/goolm/crypto"
)
const (
oneTimeKeyIdTag = 0x0A
baseKeyTag = 0x12
identityKeyTag = 0x1A
messageTag = 0x22
)
type PreKeyMessage struct {
Version byte `json:"version"`
IdentityKey crypto.Curve25519PublicKey `json:"id_key"`
BaseKey crypto.Curve25519PublicKey `json:"base_key"`
OneTimeKey crypto.Curve25519PublicKey `json:"one_time_key"`
Message []byte `json:"message"`
}
// Decodes decodes the input and populates the corresponding fileds.
func (r *PreKeyMessage) Decode(input []byte) error {
r.Version = 0
r.IdentityKey = nil
r.BaseKey = nil
r.OneTimeKey = nil
r.Message = nil
if len(input) == 0 {
return nil
}
//first Byte is always version
r.Version = input[0]
curPos := 1
for curPos < len(input) {
//Read Key
curKey, readBytes := decodeVarInt(input[curPos:])
if err := checkDecodeErr(readBytes); err != nil {
return err
}
curPos += readBytes
if (curKey & 0b111) == 0 {
//The value is of type varint
_, readBytes := decodeVarInt(input[curPos:])
if err := checkDecodeErr(readBytes); err != nil {
return err
}
curPos += readBytes
} else if (curKey & 0b111) == 2 {
//The value is of type string
value, readBytes := decodeVarString(input[curPos:])
if err := checkDecodeErr(readBytes); err != nil {
return err
}
curPos += readBytes
switch curKey {
case oneTimeKeyIdTag:
r.OneTimeKey = value
case baseKeyTag:
r.BaseKey = value
case identityKeyTag:
r.IdentityKey = value
case messageTag:
r.Message = value
}
}
}
return nil
}
// CheckField verifies the fields. If theirIdentityKey is nil, it is not compared to the key in the message.
func (r *PreKeyMessage) CheckFields(theirIdentityKey *crypto.Curve25519PublicKey) bool {
ok := true
ok = ok && (theirIdentityKey != nil || r.IdentityKey != nil)
if r.IdentityKey != nil {
ok = ok && (len(r.IdentityKey) == crypto.Curve25519KeyLength)
}
ok = ok && len(r.Message) != 0
ok = ok && len(r.BaseKey) == crypto.Curve25519KeyLength
ok = ok && len(r.OneTimeKey) == crypto.Curve25519KeyLength
return ok
}
// Encode encodes the message.
func (r *PreKeyMessage) Encode() ([]byte, error) {
var lengthOfMessage int
lengthOfMessage += 1 //Version
lengthOfMessage += encodeVarIntByteLength(oneTimeKeyIdTag) + encodeVarStringByteLength(r.OneTimeKey)
lengthOfMessage += encodeVarIntByteLength(identityKeyTag) + encodeVarStringByteLength(r.IdentityKey)
lengthOfMessage += encodeVarIntByteLength(baseKeyTag) + encodeVarStringByteLength(r.BaseKey)
lengthOfMessage += encodeVarIntByteLength(messageTag) + encodeVarStringByteLength(r.Message)
out := make([]byte, lengthOfMessage)
out[0] = r.Version
curPos := 1
encodedTag := encodeVarInt(oneTimeKeyIdTag)
copy(out[curPos:], encodedTag)
curPos += len(encodedTag)
encodedValue := encodeVarString(r.OneTimeKey)
copy(out[curPos:], encodedValue)
curPos += len(encodedValue)
encodedTag = encodeVarInt(identityKeyTag)
copy(out[curPos:], encodedTag)
curPos += len(encodedTag)
encodedValue = encodeVarString(r.IdentityKey)
copy(out[curPos:], encodedValue)
curPos += len(encodedValue)
encodedTag = encodeVarInt(baseKeyTag)
copy(out[curPos:], encodedTag)
curPos += len(encodedTag)
encodedValue = encodeVarString(r.BaseKey)
copy(out[curPos:], encodedValue)
curPos += len(encodedValue)
encodedTag = encodeVarInt(messageTag)
copy(out[curPos:], encodedTag)
curPos += len(encodedTag)
encodedValue = encodeVarString(r.Message)
copy(out[curPos:], encodedValue)
return out, nil
}

View File

@@ -0,0 +1,44 @@
package message
import (
"encoding/binary"
"fmt"
"maunium.net/go/mautrix/crypto/goolm"
"maunium.net/go/mautrix/crypto/goolm/crypto"
)
const (
sessionExportVersion = 0x01
)
// MegolmSessionExport represents a message in the session export format.
type MegolmSessionExport struct {
Counter uint32 `json:"counter"`
RatchetData [128]byte `json:"data"`
PublicKey crypto.Ed25519PublicKey `json:"public_key"`
}
// Encode returns the encoded message in the correct format.
func (s MegolmSessionExport) Encode() []byte {
output := make([]byte, 165)
output[0] = sessionExportVersion
binary.BigEndian.PutUint32(output[1:], s.Counter)
copy(output[5:], s.RatchetData[:])
copy(output[133:], s.PublicKey)
return output
}
// Decode populates the struct with the data encoded in input.
func (s *MegolmSessionExport) Decode(input []byte) error {
if len(input) != 165 {
return fmt.Errorf("decrypt: %w", goolm.ErrBadInput)
}
if input[0] != sessionExportVersion {
return fmt.Errorf("decrypt: %w", goolm.ErrBadVersion)
}
s.Counter = binary.BigEndian.Uint32(input[1:5])
copy(s.RatchetData[:], input[5:133])
s.PublicKey = input[133:]
return nil
}

View File

@@ -0,0 +1,50 @@
package message
import (
"encoding/binary"
"fmt"
"maunium.net/go/mautrix/crypto/goolm"
"maunium.net/go/mautrix/crypto/goolm/crypto"
)
const (
sessionSharingVersion = 0x02
)
// MegolmSessionSharing represents a message in the session sharing format.
type MegolmSessionSharing struct {
Counter uint32 `json:"counter"`
RatchetData [128]byte `json:"data"`
PublicKey crypto.Ed25519PublicKey `json:"-"` //only used when decrypting messages
}
// Encode returns the encoded message in the correct format with the signature by key appended.
func (s MegolmSessionSharing) EncodeAndSign(key crypto.Ed25519KeyPair) []byte {
output := make([]byte, 229)
output[0] = sessionSharingVersion
binary.BigEndian.PutUint32(output[1:], s.Counter)
copy(output[5:], s.RatchetData[:])
copy(output[133:], key.PublicKey)
signature := key.Sign(output[:165])
copy(output[165:], signature)
return output
}
// VerifyAndDecode verifies the input and populates the struct with the data encoded in input.
func (s *MegolmSessionSharing) VerifyAndDecode(input []byte) error {
if len(input) != 229 {
return fmt.Errorf("verify: %w", goolm.ErrBadInput)
}
publicKey := crypto.Ed25519PublicKey(input[133:165])
if !publicKey.Verify(input[:165], input[165:]) {
return fmt.Errorf("verify: %w", goolm.ErrBadVerification)
}
s.PublicKey = publicKey
if input[0] != sessionSharingVersion {
return fmt.Errorf("verify: %w", goolm.ErrBadVersion)
}
s.Counter = binary.BigEndian.Uint32(input[1:5])
copy(s.RatchetData[:], input[5:133])
return nil
}