summary refs log tree commit diff
path: root/vendor/maunium.net/go/mautrix/crypto/backup/encryptedsessiondata.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/maunium.net/go/mautrix/crypto/backup/encryptedsessiondata.go')
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/backup/encryptedsessiondata.go131
1 files changed, 131 insertions, 0 deletions
diff --git a/vendor/maunium.net/go/mautrix/crypto/backup/encryptedsessiondata.go b/vendor/maunium.net/go/mautrix/crypto/backup/encryptedsessiondata.go
new file mode 100644
index 0000000..ec551db
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/backup/encryptedsessiondata.go
@@ -0,0 +1,131 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package backup
+
+import (
+	"bytes"
+	"crypto/ecdh"
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/sha256"
+	"encoding/json"
+	"errors"
+
+	"go.mau.fi/util/jsonbytes"
+	"golang.org/x/crypto/hkdf"
+
+	"maunium.net/go/mautrix/crypto/aescbc"
+)
+
+var ErrInvalidMAC = errors.New("invalid MAC")
+
+// EncryptedSessionData is the encrypted session_data field of a key backup as
+// defined in [Section 11.12.3.2.2 of the Spec].
+//
+// The type parameter T represents the format of the session data contained in
+// the encrypted payload.
+//
+// [Section 11.12.3.2.2 of the Spec]: https://spec.matrix.org/v1.9/client-server-api/#backup-algorithm-mmegolm_backupv1curve25519-aes-sha2
+type EncryptedSessionData[T any] struct {
+	Ciphertext jsonbytes.UnpaddedBytes `json:"ciphertext"`
+	Ephemeral  EphemeralKey            `json:"ephemeral"`
+	MAC        jsonbytes.UnpaddedBytes `json:"mac"`
+}
+
+func calculateEncryptionParameters(sharedSecret []byte) (key, macKey, iv []byte, err error) {
+	hkdfReader := hkdf.New(sha256.New, sharedSecret, nil, nil)
+	encryptionParams := make([]byte, 80)
+	_, err = hkdfReader.Read(encryptionParams)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	return encryptionParams[:32], encryptionParams[32:64], encryptionParams[64:], nil
+}
+
+// calculateCompatMAC calculates the MAC as described in step 5 of according to
+// [Section 11.12.3.2.2] of the Spec which was updated in spec version 1.10 to
+// reflect what is actually implemented in libolm and Vodozemac.
+//
+// Libolm implemented the MAC functionality incorrectly. The MAC is computed
+// over an empty string rather than the ciphertext. Vodozemac implemented this
+// functionality the same way as libolm for compatibility. In version 1.10 of
+// the spec, the description of step 5 was updated to reflect the de-facto
+// standard of libolm and Vodozemac.
+//
+// [Section 11.12.3.2.2]: https://spec.matrix.org/v1.11/client-server-api/#backup-algorithm-mmegolm_backupv1curve25519-aes-sha2
+func calculateCompatMAC(macKey []byte) []byte {
+	hash := hmac.New(sha256.New, macKey)
+	return hash.Sum(nil)[:8]
+}
+
+// EncryptSessionData encrypts the given session data with the given recovery
+// key as defined in [Section 11.12.3.2.2 of the Spec].
+//
+// [Section 11.12.3.2.2 of the Spec]: https://spec.matrix.org/v1.9/client-server-api/#backup-algorithm-mmegolm_backupv1curve25519-aes-sha2
+func EncryptSessionData[T any](backupKey *MegolmBackupKey, sessionData T) (*EncryptedSessionData[T], error) {
+	sessionJSON, err := json.Marshal(sessionData)
+	if err != nil {
+		return nil, err
+	}
+
+	ephemeralKey, err := ecdh.X25519().GenerateKey(rand.Reader)
+	if err != nil {
+		return nil, err
+	}
+
+	sharedSecret, err := ephemeralKey.ECDH(backupKey.PublicKey())
+	if err != nil {
+		return nil, err
+	}
+
+	key, macKey, iv, err := calculateEncryptionParameters(sharedSecret)
+	if err != nil {
+		return nil, err
+	}
+
+	ciphertext, err := aescbc.Encrypt(key, iv, sessionJSON)
+	if err != nil {
+		return nil, err
+	}
+
+	return &EncryptedSessionData[T]{
+		Ciphertext: ciphertext,
+		Ephemeral:  EphemeralKey{ephemeralKey.PublicKey()},
+		MAC:        calculateCompatMAC(macKey),
+	}, nil
+}
+
+// Decrypt decrypts the [EncryptedSessionData] into a *T using the recovery key
+// by reversing the process described in [Section 11.12.3.2.2 of the Spec].
+//
+// [Section 11.12.3.2.2 of the Spec]: https://spec.matrix.org/v1.9/client-server-api/#backup-algorithm-mmegolm_backupv1curve25519-aes-sha2
+func (esd *EncryptedSessionData[T]) Decrypt(backupKey *MegolmBackupKey) (*T, error) {
+	sharedSecret, err := backupKey.ECDH(esd.Ephemeral.PublicKey)
+	if err != nil {
+		return nil, err
+	}
+
+	key, macKey, iv, err := calculateEncryptionParameters(sharedSecret)
+	if err != nil {
+		return nil, err
+	}
+
+	// Verify the MAC before decrypting.
+	if !bytes.Equal(calculateCompatMAC(macKey), esd.MAC) {
+		return nil, ErrInvalidMAC
+	}
+
+	plaintext, err := aescbc.Decrypt(key, iv, esd.Ciphertext)
+	if err != nil {
+		return nil, err
+	}
+
+	var sessionData T
+	err = json.Unmarshal(plaintext, &sessionData)
+	return &sessionData, err
+}