forked from Mirrors/gomuks

The previous commit made one attempt at fixing an issue with verifying keys, but was misguided: the issue at hand was not in attempting the wrong method of authorization, but rather what was *passed* to the method. Namely, the account password as opposed to the recovery phrase. Regardless of terminology, the latter should be used. Certain code has been restored, while the password parameter remains deleted.
169 lines
4.4 KiB
Go
169 lines
4.4 KiB
Go
package headless
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
|
|
"maunium.net/go/mautrix"
|
|
"maunium.net/go/mautrix/crypto"
|
|
"maunium.net/go/mautrix/crypto/ssss"
|
|
"maunium.net/go/mautrix/id"
|
|
|
|
"maunium.net/go/gomuks/initialize"
|
|
"maunium.net/go/gomuks/matrix"
|
|
"maunium.net/go/gomuks/ui"
|
|
)
|
|
|
|
type HeadlessConfig struct {
|
|
OutputDir, MxPassword, KeyPath, KeyPassword, RecoveryPhrase string
|
|
MxID id.UserID
|
|
}
|
|
|
|
func HeadlessInit(conf HeadlessConfig) error {
|
|
// setup package dir
|
|
os.Setenv("GOMUKS_ROOT", conf.OutputDir)
|
|
|
|
// init boilerplate
|
|
configDir, dataDir, cacheDir, downloadDir, err := initDirs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gmx := initialize.NewGomuks(ui.NewGomuksUI, configDir, dataDir, cacheDir, downloadDir)
|
|
err = gmx.StartHeadless()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// login section
|
|
_, hs, err := conf.MxID.Parse()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gmx.Config().HS = hs
|
|
if err := gmx.Matrix().InitClient(false); err != nil {
|
|
return err
|
|
} else if err = gmx.Matrix().Login(conf.MxID.String(), conf.MxPassword); err != nil {
|
|
return err
|
|
}
|
|
|
|
// key import
|
|
data, err := os.ReadFile(conf.KeyPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mach := gmx.Matrix().Crypto().(*crypto.OlmMachine)
|
|
_, _, err = mach.ImportKeys(conf.KeyPassword, data)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to import sessions: %v", err)
|
|
}
|
|
|
|
// verify (fetch)
|
|
key, err := getSSSS(mach, conf.RecoveryPhrase)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = mach.FetchCrossSigningKeysFromSSSS(key)
|
|
if err != nil {
|
|
return fmt.Errorf("Error fetching cross-signing keys: %v", err)
|
|
}
|
|
|
|
// verify (sign)
|
|
if mach.CrossSigningKeys == nil {
|
|
return fmt.Errorf("Cross-signing keys not cached")
|
|
}
|
|
|
|
err = mach.SignOwnDevice(mach.OwnIdentity())
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to self-sign: %v", err)
|
|
}
|
|
|
|
// sync
|
|
// how?
|
|
// this does too much: gmx.Matrix().Start()
|
|
//
|
|
// figbert:
|
|
// just looking to perform the initial sync. is gmx.Matrix().Client().Sync()
|
|
// the way to go? should i copy+paste the synce initialization from Start()
|
|
// and OnLogin()?
|
|
//
|
|
// tulir:
|
|
// not sure if there's any easy way to run a single sync, maybe calling
|
|
// Client.FullSyncRequest + Syncer.ProcessSync manually
|
|
resp, err := gmx.Matrix().Client().FullSyncRequest(mautrix.ReqSync{
|
|
Timeout: 30000,
|
|
Since: "",
|
|
FilterID: "",
|
|
FullState: true,
|
|
SetPresence: gmx.Matrix().Client().SyncPresence,
|
|
Context: context.Background(),
|
|
StreamResponse: true,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gmx.Matrix().(*matrix.Container).InitSyncer()
|
|
err = gmx.Matrix().(*matrix.Container).ProcessSyncResponse(resp, "")
|
|
|
|
return err
|
|
}
|
|
|
|
func initDirs() (string, string, string, string, error) {
|
|
config, err := initialize.UserConfigDir()
|
|
if err != nil {
|
|
return "", "", "", "", fmt.Errorf("Failed to get config directory: %v", err)
|
|
}
|
|
|
|
data, err := initialize.UserDataDir()
|
|
if err != nil {
|
|
return "", "", "", "", fmt.Errorf("Failed to get data directory: %v", err)
|
|
}
|
|
|
|
cache, err := initialize.UserCacheDir()
|
|
if err != nil {
|
|
return "", "", "", "", fmt.Errorf("Failed to get cache directory: %v", err)
|
|
}
|
|
|
|
download, err := initialize.UserDownloadDir()
|
|
if err != nil {
|
|
return "", "", "", "", fmt.Errorf("Failed to get download directory: %v", err)
|
|
}
|
|
|
|
return config, data, cache, download, nil
|
|
}
|
|
|
|
func getSSSS(mach *crypto.OlmMachine, recoveryPhrase string) (*ssss.Key, error) {
|
|
_, keyData, err := mach.SSSS.GetDefaultKeyData()
|
|
if err != nil {
|
|
if errors.Is(err, mautrix.MNotFound) {
|
|
return nil, fmt.Errorf("SSSS not set up, use `!ssss generate --set-default` first")
|
|
} else {
|
|
return nil, fmt.Errorf("Failed to fetch default SSSS key data: %v", err)
|
|
}
|
|
}
|
|
|
|
var key *ssss.Key
|
|
if keyData.Passphrase != nil && keyData.Passphrase.Algorithm == ssss.PassphraseAlgorithmPBKDF2 {
|
|
key, err = keyData.VerifyPassphrase(recoveryPhrase)
|
|
if errors.Is(err, ssss.ErrIncorrectSSSSKey) {
|
|
return nil, fmt.Errorf("Incorrect passphrase")
|
|
}
|
|
} else {
|
|
key, err = keyData.VerifyRecoveryKey(recoveryPhrase)
|
|
if errors.Is(err, ssss.ErrInvalidRecoveryKey) {
|
|
return nil, fmt.Errorf("Malformed recovery key")
|
|
} else if errors.Is(err, ssss.ErrIncorrectSSSSKey) {
|
|
return nil, fmt.Errorf("Incorrect recovery key")
|
|
}
|
|
}
|
|
// All the errors should already be handled above, this is just for backup
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to get SSSS key: %v", err)
|
|
}
|
|
return key, nil
|
|
}
|