feat: update access token environment variables and enhance device signing process for E2EE agents; add verification script and system flow documentation
This commit is contained in:
+104
-12
@@ -4,6 +4,7 @@
|
||||
// Usage:
|
||||
//
|
||||
// go run -tags goolm ./cmd/verify --homeserver https://... --username asistente-2 --password <pass> --token <access_token>
|
||||
// go run -tags goolm ./cmd/verify --homeserver https://... --username asistente-2 --token <access_token> # tries dummy/admin UIA
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -38,7 +39,8 @@ func main() {
|
||||
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).`,
|
||||
Requires the bot's access token. Password is optional — if omitted, tries
|
||||
dummy auth (MSC3967, Synapse 1.79+) then falls back to password if needed.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
homeserver = strings.TrimRight(homeserver, "/")
|
||||
serverName := homeserver
|
||||
@@ -102,7 +104,9 @@ Requires the bot's access token and password (for UIA during key upload).`,
|
||||
}
|
||||
|
||||
fmt.Println("→ Generating and uploading cross-signing keys...")
|
||||
recoveryKey, _, err := olmMachine.GenerateAndUploadCrossSigningKeysWithPassword(ctx, password, "")
|
||||
|
||||
// Try multiple UIA strategies in order of preference.
|
||||
recoveryKey, err := uploadCrossSigningKeys(ctx, olmMachine, password)
|
||||
if err != nil {
|
||||
// If keys already exist, try to just sign our device
|
||||
fmt.Printf(" Note: %v\n", err)
|
||||
@@ -111,41 +115,129 @@ Requires the bot's access token and password (for UIA during key upload).`,
|
||||
}
|
||||
|
||||
fmt.Println("✓ Cross-signing keys uploaded successfully")
|
||||
fmt.Printf("✓ Device %s is now verified by %s\n", client.DeviceID, userID)
|
||||
|
||||
// Sign own device immediately after uploading keys
|
||||
if signErr := signOwnDevice(ctx, olmMachine, client); signErr != nil {
|
||||
fmt.Printf(" Warning: could not auto-sign device: %v\n", signErr)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("─── IMPORTANT: Save the recovery key ───")
|
||||
fmt.Printf("SSSS_RECOVERY_KEY_%s=%s\n", strings.ToUpper(strings.ReplaceAll(username, "-", "_")), recoveryKey)
|
||||
envKey := strings.ToUpper(strings.ReplaceAll(username, "-", "_"))
|
||||
fmt.Printf("SSSS_RECOVERY_KEY_%s=%s\n", envKey, 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, "-", "_")))
|
||||
fmt.Printf(" recovery_key_env: SSSS_RECOVERY_KEY_%s\n", envKey)
|
||||
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(&password, "password", "", "Bot password (for UIA auth, optional)")
|
||||
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")
|
||||
// password is no longer required
|
||||
|
||||
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,
|
||||
// uploadCrossSigningKeys tries multiple UIA strategies to upload cross-signing keys.
|
||||
// Order: password (if provided) → dummy (MSC3967) → password with empty string.
|
||||
func uploadCrossSigningKeys(ctx context.Context, mach *crypto.OlmMachine, password string) (string, error) {
|
||||
type strategy struct {
|
||||
name string
|
||||
fn func(*mautrix.RespUserInteractive) interface{}
|
||||
}
|
||||
err := mach.SignOwnDevice(ctx, device)
|
||||
|
||||
var strategies []strategy
|
||||
|
||||
// If password provided, try it first
|
||||
if password != "" {
|
||||
strategies = append(strategies, strategy{
|
||||
name: "password auth",
|
||||
fn: func(uiResp *mautrix.RespUserInteractive) interface{} {
|
||||
return &mautrix.ReqUIAuthLogin{
|
||||
BaseAuthData: mautrix.BaseAuthData{
|
||||
Type: mautrix.AuthTypePassword,
|
||||
Session: uiResp.Session,
|
||||
},
|
||||
User: mach.Client.UserID.String(),
|
||||
Password: password,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Try dummy auth (MSC3967 — works on first upload with Synapse 1.79+)
|
||||
strategies = append(strategies, strategy{
|
||||
name: "dummy auth (MSC3967)",
|
||||
fn: func(uiResp *mautrix.RespUserInteractive) interface{} {
|
||||
return &mautrix.BaseAuthData{
|
||||
Type: mautrix.AuthTypeDummy,
|
||||
Session: uiResp.Session,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// If no password was given, also try password auth with empty as last resort
|
||||
if password == "" {
|
||||
strategies = append(strategies, strategy{
|
||||
name: "empty password auth",
|
||||
fn: func(uiResp *mautrix.RespUserInteractive) interface{} {
|
||||
return &mautrix.ReqUIAuthLogin{
|
||||
BaseAuthData: mautrix.BaseAuthData{
|
||||
Type: mautrix.AuthTypePassword,
|
||||
Session: uiResp.Session,
|
||||
},
|
||||
User: mach.Client.UserID.String(),
|
||||
Password: " ", // non-empty to avoid omitempty dropping the field
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, s := range strategies {
|
||||
fmt.Printf(" Trying %s...\n", s.name)
|
||||
recoveryKey, _, err := mach.GenerateAndUploadCrossSigningKeys(ctx, s.fn, "")
|
||||
if err == nil {
|
||||
fmt.Printf(" ✓ Succeeded with %s\n", s.name)
|
||||
return recoveryKey, nil
|
||||
}
|
||||
fmt.Printf(" ✗ %s failed: %v\n", s.name, err)
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
return "", lastErr
|
||||
}
|
||||
|
||||
func signOwnDevice(ctx context.Context, mach *crypto.OlmMachine, client *mautrix.Client) error {
|
||||
// Force-fetch own device keys from the server so the local store has
|
||||
// the correct signing key. Without this, SignOwnDevice fails with
|
||||
// "received update for device with different signing key (expected , got X)".
|
||||
devices, err := mach.FetchKeys(ctx, []id.UserID{client.UserID}, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch own device keys: %w", err)
|
||||
}
|
||||
|
||||
userDevices, ok := devices[client.UserID]
|
||||
if !ok {
|
||||
return fmt.Errorf("own user %s not found in fetched keys", client.UserID)
|
||||
}
|
||||
device, ok := userDevices[client.DeviceID]
|
||||
if !ok {
|
||||
return fmt.Errorf("own device %s not found in fetched keys", client.DeviceID)
|
||||
}
|
||||
|
||||
if err := mach.SignOwnDevice(ctx, device); err != nil {
|
||||
return fmt.Errorf("sign own device: %w", err)
|
||||
}
|
||||
fmt.Printf("✓ Device %s signed with cross-signing key\n", client.DeviceID)
|
||||
|
||||
Reference in New Issue
Block a user