Files
agents_and_robots/cmd/verify/main.go
T

154 lines
5.0 KiB
Go

// Command verify sets up cross-signing keys for a Matrix bot user.
// This eliminates the "Encrypted by a device not verified by its owner" warning.
//
// Usage:
//
// go run -tags goolm ./cmd/verify --homeserver https://... --username asistente-2 --password <pass> --token <access_token>
package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto"
"maunium.net/go/mautrix/crypto/cryptohelper"
"maunium.net/go/mautrix/id"
)
func main() {
var (
homeserver string
username string
password string
token string
storePath string
pickleKeyHex string
)
root := &cobra.Command{
Use: "verify",
Short: "Set up cross-signing keys for a Matrix bot",
Long: `Generates and uploads cross-signing keys so the bot's device is verified.
This removes the "Encrypted by a device not verified by its owner" warning.
Requires the bot's access token and password (for UIA during key upload).`,
RunE: func(cmd *cobra.Command, args []string) error {
homeserver = strings.TrimRight(homeserver, "/")
serverName := homeserver
serverName = strings.TrimPrefix(serverName, "https://")
serverName = strings.TrimPrefix(serverName, "http://")
userID := id.UserID(fmt.Sprintf("@%s:%s", username, serverName))
fmt.Printf("→ Setting up cross-signing for %s\n", userID)
// Create mautrix client
client, err := mautrix.NewClient(homeserver, userID, token)
if err != nil {
return fmt.Errorf("create client: %w", err)
}
ctx := context.Background()
// Resolve device ID
whoami, err := client.Whoami(ctx)
if err != nil {
return fmt.Errorf("whoami: %w", err)
}
client.DeviceID = whoami.DeviceID
fmt.Printf("→ Device ID: %s\n", client.DeviceID)
// Initialize crypto — use explicit pickle key if provided, else sha256(token)
var pickleKey []byte
if pickleKeyHex != "" {
var err error
pickleKey, err = hex.DecodeString(pickleKeyHex)
if err != nil {
return fmt.Errorf("decode pickle-key hex: %w", err)
}
} else {
sum := sha256.Sum256([]byte(token))
pickleKey = sum[:]
}
dbPath := filepath.Join(storePath, "crypto.db")
if err := os.MkdirAll(filepath.Dir(dbPath), 0700); err != nil {
return fmt.Errorf("create store dir: %w", err)
}
helper, err := cryptohelper.NewCryptoHelper(client, pickleKey, dbPath)
if err != nil {
return fmt.Errorf("create crypto helper: %w", err)
}
helper.DBAccountID = username
if err := helper.Init(ctx); err != nil {
return fmt.Errorf("init crypto: %w", err)
}
defer helper.Close()
client.Crypto = helper
// Get the OlmMachine to generate cross-signing keys
olmMachine := helper.Machine()
if olmMachine == nil {
return fmt.Errorf("olm machine not available")
}
fmt.Println("→ Generating and uploading cross-signing keys...")
recoveryKey, _, err := olmMachine.GenerateAndUploadCrossSigningKeysWithPassword(ctx, password, "")
if err != nil {
// If keys already exist, try to just sign our device
fmt.Printf(" Note: %v\n", err)
fmt.Println("→ Attempting to sign own device with existing keys...")
return signOwnDevice(ctx, olmMachine, client)
}
fmt.Println("✓ Cross-signing keys uploaded successfully")
fmt.Printf("✓ Device %s is now verified by %s\n", client.DeviceID, userID)
fmt.Println()
fmt.Println("─── IMPORTANT: Save the recovery key ───")
fmt.Printf("SSSS_RECOVERY_KEY_%s=%s\n", strings.ToUpper(strings.ReplaceAll(username, "-", "_")), recoveryKey)
fmt.Println()
fmt.Println("Add this to your .env file and set recovery_key_env in the agent's config.yaml:")
fmt.Println(" encryption:")
fmt.Printf(" recovery_key_env: SSSS_RECOVERY_KEY_%s\n", strings.ToUpper(strings.ReplaceAll(username, "-", "_")))
return nil
},
}
root.Flags().StringVar(&homeserver, "homeserver", "", "Matrix homeserver URL")
root.Flags().StringVar(&username, "username", "", "Bot username (without @ or server)")
root.Flags().StringVar(&password, "password", "", "Bot password (for UIA auth)")
root.Flags().StringVar(&token, "token", "", "Bot access token")
root.Flags().StringVar(&storePath, "store", "./data/verify-crypto/", "Crypto store path")
root.Flags().StringVar(&pickleKeyHex, "pickle-key", "", "Hex-encoded pickle key (must match agent's pickle key if sharing crypto store)")
_ = root.MarkFlagRequired("homeserver")
_ = root.MarkFlagRequired("username")
_ = root.MarkFlagRequired("password")
_ = root.MarkFlagRequired("token")
if err := root.Execute(); err != nil {
os.Exit(1)
}
}
func signOwnDevice(ctx context.Context, mach *crypto.OlmMachine, client *mautrix.Client) error {
device := &id.Device{
UserID: client.UserID,
DeviceID: client.DeviceID,
}
err := mach.SignOwnDevice(ctx, device)
if err != nil {
return fmt.Errorf("sign own device: %w", err)
}
fmt.Printf("✓ Device %s signed with cross-signing key\n", client.DeviceID)
return nil
}