nyxmuks/pkg/hicli/verify.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
}