167 lines
5.6 KiB
Go
167 lines
5.6 KiB
Go
// Copyright (c) 2024 Tulir Asokan
|
|
//
|
|
// 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 hicli
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/rs/zerolog"
|
|
"maunium.net/go/mautrix/crypto"
|
|
"maunium.net/go/mautrix/crypto/backup"
|
|
"maunium.net/go/mautrix/crypto/ssss"
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/id"
|
|
)
|
|
|
|
func (h *HiClient) checkIsCurrentDeviceVerified(ctx context.Context) (bool, error) {
|
|
keys := h.Crypto.GetOwnCrossSigningPublicKeys(ctx)
|
|
if keys == nil {
|
|
return false, fmt.Errorf("own cross-signing keys not found")
|
|
}
|
|
isVerified, err := h.Crypto.CryptoStore.IsKeySignedBy(ctx, h.Account.UserID, h.Crypto.GetAccount().SigningKey(), h.Account.UserID, keys.SelfSigningKey)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to check if current device is signed by own self-signing key: %w", err)
|
|
}
|
|
return isVerified, nil
|
|
}
|
|
|
|
func (h *HiClient) fetchKeyBackupKey(ctx context.Context, ssssKey *ssss.Key) error {
|
|
latestVersion, err := h.Client.GetKeyBackupLatestVersion(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get key backup latest version: %w", err)
|
|
}
|
|
h.KeyBackupVersion = latestVersion.Version
|
|
data, err := h.Crypto.SSSS.GetDecryptedAccountData(ctx, event.AccountDataMegolmBackupKey, ssssKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get megolm backup key from SSSS: %w", err)
|
|
}
|
|
key, err := backup.MegolmBackupKeyFromBytes(data)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse megolm backup key: %w", err)
|
|
}
|
|
err = h.CryptoStore.PutSecret(ctx, id.SecretMegolmBackupV1, base64.StdEncoding.EncodeToString(key.Bytes()))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to store megolm backup key: %w", err)
|
|
}
|
|
h.KeyBackupKey = key
|
|
return nil
|
|
}
|
|
|
|
func (h *HiClient) getAndDecodeSecret(ctx context.Context, secret id.Secret) ([]byte, error) {
|
|
secretData, err := h.CryptoStore.GetSecret(ctx, secret)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get secret %s: %w", secret, err)
|
|
} else if secretData == "" {
|
|
return nil, fmt.Errorf("secret %s not found", secret)
|
|
}
|
|
data, err := base64.StdEncoding.DecodeString(secretData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode secret %s: %w", secret, err)
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func (h *HiClient) loadPrivateKeys(ctx context.Context) error {
|
|
zerolog.Ctx(ctx).Debug().Msg("Loading cross-signing private keys")
|
|
masterKeySeed, err := h.getAndDecodeSecret(ctx, id.SecretXSMaster)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get master key: %w", err)
|
|
}
|
|
selfSigningKeySeed, err := h.getAndDecodeSecret(ctx, id.SecretXSSelfSigning)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get self-signing key: %w", err)
|
|
}
|
|
userSigningKeySeed, err := h.getAndDecodeSecret(ctx, id.SecretXSUserSigning)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get user signing key: %w", err)
|
|
}
|
|
err = h.Crypto.ImportCrossSigningKeys(crypto.CrossSigningSeeds{
|
|
MasterKey: masterKeySeed,
|
|
SelfSigningKey: selfSigningKeySeed,
|
|
UserSigningKey: userSigningKeySeed,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to import cross-signing private keys: %w", err)
|
|
}
|
|
zerolog.Ctx(ctx).Debug().Msg("Loading key backup key")
|
|
keyBackupKey, err := h.getAndDecodeSecret(ctx, id.SecretMegolmBackupV1)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get megolm backup key: %w", err)
|
|
}
|
|
h.KeyBackupKey, err = backup.MegolmBackupKeyFromBytes(keyBackupKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse megolm backup key: %w", err)
|
|
}
|
|
zerolog.Ctx(ctx).Debug().Msg("Fetching key backup version")
|
|
latestVersion, err := h.Client.GetKeyBackupLatestVersion(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get key backup latest version: %w", err)
|
|
}
|
|
h.KeyBackupVersion = latestVersion.Version
|
|
zerolog.Ctx(ctx).Debug().Msg("Secrets loaded")
|
|
return nil
|
|
}
|
|
|
|
func (h *HiClient) storeCrossSigningPrivateKeys(ctx context.Context) error {
|
|
keys := h.Crypto.CrossSigningKeys
|
|
err := h.CryptoStore.PutSecret(ctx, id.SecretXSMaster, base64.StdEncoding.EncodeToString(keys.MasterKey.Seed()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = h.CryptoStore.PutSecret(ctx, id.SecretXSSelfSigning, base64.StdEncoding.EncodeToString(keys.SelfSigningKey.Seed()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = h.CryptoStore.PutSecret(ctx, id.SecretXSUserSigning, base64.StdEncoding.EncodeToString(keys.UserSigningKey.Seed()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *HiClient) Verify(ctx context.Context, code string) error {
|
|
defer h.dispatchCurrentState()
|
|
keyID, keyData, err := h.Crypto.SSSS.GetDefaultKeyData(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get default SSSS key data: %w", err)
|
|
}
|
|
key, err := keyData.VerifyRecoveryKey(keyID, code)
|
|
if errors.Is(err, ssss.ErrInvalidRecoveryKey) && keyData.Passphrase != nil {
|
|
key, err = keyData.VerifyPassphrase(keyID, code)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = h.Crypto.FetchCrossSigningKeysFromSSSS(ctx, key)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch cross-signing keys from SSSS: %w", err)
|
|
}
|
|
err = h.Crypto.SignOwnDevice(ctx, h.Crypto.OwnIdentity())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to sign own device: %w", err)
|
|
}
|
|
err = h.Crypto.SignOwnMasterKey(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to sign own master key: %w", err)
|
|
}
|
|
err = h.storeCrossSigningPrivateKeys(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to store cross-signing private keys: %w", err)
|
|
}
|
|
err = h.fetchKeyBackupKey(ctx, key)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch key backup key: %w", err)
|
|
}
|
|
h.Verified = true
|
|
if !h.IsSyncing() {
|
|
go h.Sync()
|
|
}
|
|
return nil
|
|
}
|