feat: add recovery key support for E2EE agents, including configuration and documentation updates

This commit is contained in:
2026-03-05 00:56:15 +00:00
parent fc234bcb92
commit 0f900d1560
10 changed files with 284 additions and 35 deletions
+61
View File
@@ -15,6 +15,7 @@ import (
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto"
"maunium.net/go/mautrix/crypto/cryptohelper"
"maunium.net/go/mautrix/crypto/ssss"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
@@ -108,6 +109,66 @@ func (c *Client) InitCrypto(ctx context.Context, storePath, pickleKeyHex, agentI
return closer, nil
}
// ssssKeyFetcher abstracts the SSSS + cross-signing key retrieval for testing.
type ssssKeyFetcher interface {
GetDefaultKeyData(ctx context.Context) (string, ssssKeyVerifier, error)
FetchCrossSigningKeysFromSSSS(ctx context.Context, key *ssss.Key) error
}
// ssssKeyVerifier abstracts the SSSS key metadata verification.
type ssssKeyVerifier interface {
VerifyRecoveryKey(keyID, recoveryKey string) (*ssss.Key, error)
}
// olmSSSSFetcher adapts *crypto.OlmMachine to the ssssKeyFetcher interface.
type olmSSSSFetcher struct {
machine *crypto.OlmMachine
}
func (o *olmSSSSFetcher) GetDefaultKeyData(ctx context.Context) (string, ssssKeyVerifier, error) {
keyID, keyData, err := o.machine.SSSS.GetDefaultKeyData(ctx)
return keyID, keyData, err
}
func (o *olmSSSSFetcher) FetchCrossSigningKeysFromSSSS(ctx context.Context, key *ssss.Key) error {
return o.machine.FetchCrossSigningKeysFromSSSS(ctx, key)
}
// FetchCrossSigningKeys retrieves cross-signing private keys from SSSS
// (server-side secret storage) using the given base58 recovery key.
// This allows the agent to sign its own device, eliminating the
// "Encrypted by a device not verified by its owner" warning.
func (c *Client) FetchCrossSigningKeys(ctx context.Context, recoveryKey string) error {
wrapper, ok := c.raw.Crypto.(*mautrixCryptoWrapper)
if !ok || wrapper == nil {
return fmt.Errorf("crypto not initialized")
}
machine := wrapper.Machine()
if machine == nil {
return fmt.Errorf("olm machine not available")
}
return fetchCrossSigningKeysCore(ctx, &olmSSSSFetcher{machine}, recoveryKey)
}
// fetchCrossSigningKeysCore contains the testable logic for SSSS key retrieval.
func fetchCrossSigningKeysCore(ctx context.Context, fetcher ssssKeyFetcher, recoveryKey string) error {
keyID, keyData, err := fetcher.GetDefaultKeyData(ctx)
if err != nil {
return fmt.Errorf("get SSSS default key: %w", err)
}
key, err := keyData.VerifyRecoveryKey(keyID, recoveryKey)
if err != nil {
return fmt.Errorf("verify recovery key: %w", err)
}
if err := fetcher.FetchCrossSigningKeysFromSSSS(ctx, key); err != nil {
return fmt.Errorf("fetch cross-signing keys from SSSS: %w", err)
}
return nil
}
// initCryptoCore contains the testable logic: pickle key resolution, store
// creation, and auto-recovery on stale crypto.db. Returns (closer, helper, err).
func initCryptoCore(ctx context.Context, storePath, pickleKeyHex, accessToken, agentID string, initer cryptoIniter, logger *slog.Logger) (io.Closer, cryptoHelper, error) {