package main import ( "context" "encoding/json" "flag" "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "git.julianfamily.org/keepassgo/internal/browserbridge" "git.julianfamily.org/keepassgo/internal/grpcaddr" ) func main() { if len(os.Args) > 1 { switch strings.TrimSpace(os.Args[1]) { case "install-native-host": if err := runInstallNativeHost(os.Args[2:]); err != nil { fail(err) } return case "status": if err := runStatus(os.Args[2:]); err != nil { fail(err) } return } } if err := runNativeMessage(); err != nil { _ = browserbridge.WriteResponse(os.Stdout, browserbridge.Response{Success: false, Error: err.Error()}) os.Exit(1) } } func runInstallNativeHost(args []string) error { fs := flag.NewFlagSet("install-native-host", flag.ContinueOnError) browserName := fs.String("browser", string(browserbridge.BrowserFirefox), "target browser: firefox, chrome, chromium") binaryPath := fs.String("binary", "", "path to keepassgo-browser-bridge binary") extensionID := fs.String("extension-id", "", "browser extension id (required for chrome/chromium)") extensionKey := fs.String("extension-key", "", "Chromium manifest public key used to derive a fixed extension id") extensionKeyFile := fs.String("extension-key-file", "", "path to a Chromium manifest public key file") outputPath := fs.String("output", "", "native host manifest output path") if err := fs.Parse(args); err != nil { return err } path := strings.TrimSpace(*binaryPath) if path == "" { resolved, err := defaultBinaryPath() if err != nil { return err } path = resolved } resolvedExtensionID := strings.TrimSpace(*extensionID) if resolvedExtensionID == "" { keyValue := strings.TrimSpace(*extensionKey) if keyValue == "" && strings.TrimSpace(*extensionKeyFile) != "" { data, err := os.ReadFile(strings.TrimSpace(*extensionKeyFile)) if err != nil { return err } keyValue = string(data) } if keyValue != "" { derivedID, err := browserbridge.ChromiumExtensionIDFromManifestKey(keyValue) if err != nil { return err } resolvedExtensionID = derivedID } } installed, err := browserbridge.InstallManifest(browserbridge.Browser(strings.TrimSpace(*browserName)), path, resolvedExtensionID, strings.TrimSpace(*outputPath)) if err != nil { return err } fmt.Fprintln(os.Stdout, installed) return nil } func runStatus(args []string) error { fs := flag.NewFlagSet("status", flag.ContinueOnError) grpcAddr := fs.String("grpc-addr", grpcaddr.Default(runtime.GOOS), "KeePassGO local gRPC address") token := fs.String("token", "", "KeePassGO API bearer token") if err := fs.Parse(args); err != nil { return err } req := browserbridge.Request{ Action: "status", GRPCAddress: strings.TrimSpace(*grpcAddr), BearerToken: strings.TrimSpace(*token), } connCfg, err := req.Connection() if err != nil { return err } conn, client, ctx, err := browserbridge.Dial(context.Background(), connCfg) if err != nil { return err } defer func() { _ = conn.Close() }() resp := browserbridge.HandleRequest(ctx, req, client) enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(resp) } func runNativeMessage() error { req, err := browserbridge.ReadRequest(os.Stdin) if err != nil { return err } connCfg, err := req.Connection() if err != nil { return browserbridge.WriteResponse(os.Stdout, browserbridge.Response{Success: false, Error: err.Error()}) } conn, client, ctx, err := browserbridge.Dial(context.Background(), connCfg) if err != nil { return browserbridge.WriteResponse(os.Stdout, browserbridge.Response{Success: false, Error: err.Error()}) } defer func() { _ = conn.Close() }() return browserbridge.WriteResponse(os.Stdout, browserbridge.HandleRequest(ctx, req, client)) } func defaultBinaryPath() (string, error) { self, err := os.Executable() if err == nil && strings.TrimSpace(self) != "" { return self, nil } self, err = exec.LookPath("keepassgo-browser-bridge") if err == nil { return self, nil } return filepath.Abs("./keepassgo-browser-bridge") } func fail(err error) { fmt.Fprintln(os.Stderr, err) os.Exit(1) }