Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto/aessha2: extract AES SHA2 functionality from crypto/goolm/cipher #300

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions crypto/aessha2/aessha2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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 aessha2 implements the m.megolm.v1.aes-sha2 encryption algorithm
// described in [Section 10.12.4.3] in the Spec
//
// [Section 10.12.4.3]: https://spec.matrix.org/v1.12/client-server-api/#mmegolmv1aes-sha2
package aessha2

import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"io"

"golang.org/x/crypto/hkdf"

"maunium.net/go/mautrix/crypto/aescbc"
)

type AESSHA2 struct {
aesKey, hmacKey, iv []byte
}

func NewAESSHA2(secret, info []byte) (AESSHA2, error) {
kdf := hkdf.New(sha256.New, secret, nil, info)
keymatter := make([]byte, 80)
_, err := io.ReadFull(kdf, keymatter)
return AESSHA2{
keymatter[:32], // AES Key
keymatter[32:64], // HMAC Key
keymatter[64:], // IV
}, err
}

func (a *AESSHA2) Encrypt(plaintext []byte) ([]byte, error) {
return aescbc.Encrypt(a.aesKey, a.iv, plaintext)
}

func (a *AESSHA2) Decrypt(ciphertext []byte) ([]byte, error) {
return aescbc.Decrypt(a.aesKey, a.iv, ciphertext)
}

func (a *AESSHA2) MAC(ciphertext []byte) ([]byte, error) {
hash := hmac.New(sha256.New, a.hmacKey)
_, err := hash.Write(ciphertext)
return hash.Sum(nil), err
}

func (a *AESSHA2) VerifyMAC(ciphertext, theirMAC []byte) (bool, error) {
if mac, err := a.MAC(ciphertext); err != nil {
return false, err
} else {
return subtle.ConstantTimeCompare(mac[:len(theirMAC)], theirMAC) == 1, nil
}
}
33 changes: 33 additions & 0 deletions crypto/aessha2/aessha2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package aessha2_test

import (
"crypto/aes"
"testing"

"github.com/stretchr/testify/assert"

"maunium.net/go/mautrix/crypto/aessha2"
)

func TestCipherAESSha256(t *testing.T) {
key := []byte("test key")
cipher, err := aessha2.NewAESSHA2(key, []byte("testKDFinfo"))
assert.NoError(t, err)
message := []byte("this is a random message for testing the implementation")
//increase to next block size
for len(message)%aes.BlockSize != 0 {
message = append(message, []byte("-")...)
}
encrypted, err := cipher.Encrypt([]byte(message))
assert.NoError(t, err)
mac, err := cipher.MAC(encrypted)
assert.NoError(t, err)

verified, err := cipher.VerifyMAC(encrypted, mac[:8])
assert.NoError(t, err)
assert.True(t, verified, "signature verification failed")

resultPlainText, err := cipher.Decrypt(encrypted)
assert.NoError(t, err)
assert.Equal(t, message, resultPlainText)
}
10 changes: 4 additions & 6 deletions crypto/goolm/account/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ import (

"maunium.net/go/mautrix/id"

"maunium.net/go/mautrix/crypto/goolm/cipher"
"maunium.net/go/mautrix/crypto/goolm/crypto"
"maunium.net/go/mautrix/crypto/goolm/libolmpickle"
"maunium.net/go/mautrix/crypto/goolm/session"
"maunium.net/go/mautrix/crypto/goolm/utilities"
"maunium.net/go/mautrix/crypto/olm"
)

Expand Down Expand Up @@ -76,12 +74,12 @@ func NewAccount() (*Account, error) {

// PickleAsJSON returns an Account as a base64 string encrypted using the supplied key. The unencrypted representation of the Account is in JSON format.
func (a *Account) PickleAsJSON(key []byte) ([]byte, error) {
return utilities.PickleAsJSON(a, accountPickleVersionJSON, key)
return libolmpickle.PickleAsJSON(a, accountPickleVersionJSON, key)
}

// UnpickleAsJSON updates an Account by a base64 encrypted string using the supplied key. The unencrypted representation has to be in JSON format.
func (a *Account) UnpickleAsJSON(pickled, key []byte) error {
return utilities.UnpickleAsJSON(a, pickled, key, accountPickleVersionJSON)
return libolmpickle.UnpickleAsJSON(a, pickled, key, accountPickleVersionJSON)
}

// IdentityKeysJSON returns the public parts of the identity keys for the Account in a JSON string.
Expand Down Expand Up @@ -322,7 +320,7 @@ func (a *Account) ForgetOldFallbackKey() {
// Unpickle decodes the base64 encoded string and decrypts the result with the key.
// The decrypted value is then passed to UnpickleLibOlm.
func (a *Account) Unpickle(pickled, key []byte) error {
decrypted, err := cipher.Unpickle(key, pickled)
decrypted, err := libolmpickle.Unpickle(key, pickled)
if err != nil {
return err
}
Expand Down Expand Up @@ -410,7 +408,7 @@ func (a *Account) Pickle(key []byte) ([]byte, error) {
if len(key) == 0 {
return nil, olm.ErrNoKeyProvided
}
return cipher.Pickle(key, a.PickleLibOlm())
return libolmpickle.Pickle(key, a.PickleLibOlm())
}

// PickleLibOlm pickles the [Account] and returns the raw bytes.
Expand Down
81 changes: 0 additions & 81 deletions crypto/goolm/cipher/aes_sha256.go

This file was deleted.

60 changes: 0 additions & 60 deletions crypto/goolm/cipher/aes_sha256_test.go

This file was deleted.

18 changes: 0 additions & 18 deletions crypto/goolm/cipher/cipher.go

This file was deleted.

55 changes: 0 additions & 55 deletions crypto/goolm/cipher/pickle.go

This file was deleted.

28 changes: 0 additions & 28 deletions crypto/goolm/cipher/pickle_test.go

This file was deleted.

Loading