From 9cc35c912bda26f820ee27fb7024e3cc58f44fef Mon Sep 17 00:00:00 2001 From: Eddy Filip Date: Thu, 30 Oct 2025 18:16:18 +0100 Subject: [PATCH 1/4] Rename shared_lib_core.go to shared_lib_core_unix.go This shows that the file contains the implementation of interacting with the shared lib for UNIX systems (MacOS and Linux) --- internal/{shared_lib_core.go => shared_lib_core_unix.go} | 2 ++ 1 file changed, 2 insertions(+) rename internal/{shared_lib_core.go => shared_lib_core_unix.go} (99%) diff --git a/internal/shared_lib_core.go b/internal/shared_lib_core_unix.go similarity index 99% rename from internal/shared_lib_core.go rename to internal/shared_lib_core_unix.go index c2eeeb0..497132b 100644 --- a/internal/shared_lib_core.go +++ b/internal/shared_lib_core_unix.go @@ -1,3 +1,5 @@ +//go:build darwin || linux + package internal import ( From 908608b34316f8392498b7bcf3e759406f97dc5e Mon Sep 17 00:00:00 2001 From: Eddy Filip Date: Thu, 30 Oct 2025 18:24:15 +0100 Subject: [PATCH 2/4] Add interaction with shared lib for Windows --- go.mod | 5 +- go.sum | 2 + internal/shared_lib_core_windows.go | 186 ++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 internal/shared_lib_core_windows.go diff --git a/go.mod b/go.mod index 7545152..7fd8eb1 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/1password/onepassword-sdk-go -go 1.22.0 - -toolchain go1.22.5 +go 1.24.0 require ( github.com/extism/go-sdk v1.7.0 @@ -18,6 +16,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect + golang.org/x/sys v0.37.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 69904ca..dde5c6f 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZB github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= diff --git a/internal/shared_lib_core_windows.go b/internal/shared_lib_core_windows.go new file mode 100644 index 0000000..63314cf --- /dev/null +++ b/internal/shared_lib_core_windows.go @@ -0,0 +1,186 @@ +//go:build windows + +package internal + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + "os" + "path" + "unsafe" + + "golang.org/x/sys/windows" +) + +type SharedLibCore struct { + accountName string + dll *windows.DLL + procSend *windows.Proc + procFree *windows.Proc +} + +var coreLib *SharedLibCore + +// Request/Response mirror your Unix file (kept identical) +type Request struct { + Kind string `json:"kind"` + AccountName string `json:"account_name"` + Payload []byte `json:"payload"` +} + +type Response struct { + Success bool `json:"success"` + Payload []byte `json:"payload"` +} + +func (r Response) Error() string { return string(r.Payload) } + +// find1PasswordLibPath returns the path to the DLL on Windows +func find1PasswordLibPath() (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + + locations := []string{ + path.Join(home, `AppData\Local\1Password\app\8\op_sdk_ipc_client.dll`), + `C:\Program Files\1Password\op_sdk_ipc_client.dll`, + `C:\Program Files (x86)\1Password\op_sdk_ipc_client.dll`, + } + for _, p := range locations { + if _, err := os.Stat(p); err == nil { + return p, nil + } + } + return "", fmt.Errorf("1Password desktop application not found") +} + +// API identical to Unix version +func GetSharedLibCore(accountName string) (*CoreWrapper, error) { + if coreLib == nil { + path, err := find1PasswordLibPath() + if err != nil { + return nil, err + } + coreLib, err = loadCore(path) + if err != nil { + return nil, err + } + coreLib.accountName = accountName + } + coreWrapper := CoreWrapper{InnerCore: coreLib} + return &coreWrapper, nil +} + +func loadCore(path string) (*SharedLibCore, error) { + dll, err := windows.LoadDLL(path) // absolute path avoids search path surprises + if err != nil { + return nil, err + } + send, err := dll.FindProc("op_sdk_ipc_send_message") + if err != nil { + dll.Release() + return nil, errors.New("failed to load send_message") + } + free, err := dll.FindProc("op_sdk_ipc_free_response") + if err != nil { + dll.Release() + return nil, errors.New("failed to load free_message") + } + return &SharedLibCore{ + dll: dll, + procSend: send, + procFree: free, + }, nil +} + +// InitClient creates a client instance in the current core module and returns its unique ID. +func (slc *SharedLibCore) InitClient(ctx context.Context, config []byte) ([]byte, error) { + const kind = "init_client" + req := Request{ + Kind: kind, + AccountName: slc.accountName, + Payload: config, + } + input, err := json.Marshal(req) + if err != nil { + return nil, err + } + return slc.callSharedLibrary(input) +} + +func (slc *SharedLibCore) Invoke(ctx context.Context, invokeConfig []byte) ([]byte, error) { + const kind = "invoke" + req := Request{ + Kind: kind, + AccountName: slc.accountName, + Payload: invokeConfig, + } + input, err := json.Marshal(req) + if err != nil { + return nil, err + } + return slc.callSharedLibrary(input) +} + +// ReleaseClient releases memory in the core associated with the given client ID. +func (slc *SharedLibCore) ReleaseClient(clientID []byte) { + _, err := slc.callSharedLibrary(clientID) + if err != nil { + log.Println("failed to release client") + } +} + +func (slc *SharedLibCore) callSharedLibrary(input []byte) ([]byte, error) { + if len(input) == 0 { + return nil, errors.New("internal: empty input") + } + + // Signature we’re calling (from your Rust exports): + // int32_t op_sdk_ipc_send_message(const uint8_t* msg_ptr, size_t msg_len, + // uint8_t** out_buf, size_t* out_len, size_t* out_cap); + var outBuf *byte + var outLen uintptr + var outCap uintptr + + r1, _, callErr := slc.procSend.Call( + uintptr(unsafe.Pointer(&input[0])), + uintptr(len(input)), + uintptr(unsafe.Pointer(&outBuf)), + uintptr(unsafe.Pointer(&outLen)), + uintptr(unsafe.Pointer(&outCap)), + ) + // syscall layer error? + if callErr != nil && callErr != windows.ERROR_SUCCESS { + return nil, callErr + } + // library-level return code + if int32(r1) != 0 { + return nil, fmt.Errorf("failed to send message to Desktop App. Return code: %d", int32(r1)) + } + + // Copy response out of the DLL buffer, then free via exported function + resp := unsafe.Slice(outBuf, outLen) + out := make([]byte, outLen) + copy(out, resp) + + // void op_sdk_ipc_free_response(uint8_t* buf, size_t len, size_t cap); + _, _, _ = slc.procFree.Call( + uintptr(unsafe.Pointer(outBuf)), + outLen, + outCap, + ) + + // Match Unix: decode envelope and return payload or error + var response Response + if err := json.Unmarshal(out, &response); err != nil { + return nil, err + } + if response.Success { + return response.Payload, nil + } + return nil, response +} From 86c33b7b6b2685f807f8e61115213c32cd673fd5 Mon Sep 17 00:00:00 2001 From: Eddy Filip Date: Fri, 31 Oct 2025 13:51:09 +0100 Subject: [PATCH 3/4] Add paths for the SDK IPC client dll --- internal/shared_lib_core_windows.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/shared_lib_core_windows.go b/internal/shared_lib_core_windows.go index 63314cf..65f2187 100644 --- a/internal/shared_lib_core_windows.go +++ b/internal/shared_lib_core_windows.go @@ -46,9 +46,10 @@ func find1PasswordLibPath() (string, error) { } locations := []string{ + path.Join(home, `AppData\Local\1Password\op_sdk_ipc_client.dll`), + `C:\Program Files\1Password\app\8\op_sdk_ipc_client.dll`, + `C:\Program Files (x86)\1Password\app\8\op_sdk_ipc_client.dll`, path.Join(home, `AppData\Local\1Password\app\8\op_sdk_ipc_client.dll`), - `C:\Program Files\1Password\op_sdk_ipc_client.dll`, - `C:\Program Files (x86)\1Password\op_sdk_ipc_client.dll`, } for _, p := range locations { if _, err := os.Stat(p); err == nil { From 97c953121dbe56a27e68f5053b4536610c16e631 Mon Sep 17 00:00:00 2001 From: Eddy Filip Date: Fri, 31 Oct 2025 15:16:23 +0100 Subject: [PATCH 4/4] Factor out common functionality Extract common functionality present in both implementations for interacting with the shared lib --- internal/shared_lib_core.go | 138 ++++++++++++++++++++++++++++ internal/shared_lib_core_unix.go | 124 ------------------------- internal/shared_lib_core_windows.go | 97 +------------------ 3 files changed, 140 insertions(+), 219 deletions(-) create mode 100644 internal/shared_lib_core.go diff --git a/internal/shared_lib_core.go b/internal/shared_lib_core.go new file mode 100644 index 0000000..9b36d2b --- /dev/null +++ b/internal/shared_lib_core.go @@ -0,0 +1,138 @@ +package internal + +import ( + "context" + "encoding/json" + "fmt" + "log" + "os" + "path" + "runtime" +) + +// Request/Response mirror your Unix file (kept identical) +type Request struct { + Kind string `json:"kind"` + AccountName string `json:"account_name"` + Payload []byte `json:"payload"` +} + +type Response struct { + Success bool `json:"success"` + Payload []byte `json:"payload"` +} + +func (r Response) Error() string { return string(r.Payload) } + +// find1PasswordLibPath returns the path to the 1Password shared library +// (libop_sdk_ipc_client.dylib/.so/.dll) depending on OS. +func find1PasswordLibPath() (string, error) { + var locations []string + + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + + switch runtime.GOOS { + case "darwin": + locations = []string{ + "/Applications/1Password.app/Contents/Frameworks/libop_sdk_ipc_client.dylib", + path.Join(home, "Applications/1Password.app/Contents/Frameworks/libop_sdk_ipc_client.dylib"), + } + + case "linux": + locations = []string{ + "/usr/bin/1password/libop_sdk_ipc_client.so", + "/opt/1Password/libop_sdk_ipc_client.so", + "/snap/bin/1password/libop_sdk_ipc_client.so", + } + + case "windows": + locations = []string{ + path.Join(home, `AppData\Local\1Password\op_sdk_ipc_client.dll`), + `C:\Program Files\1Password\app\8\op_sdk_ipc_client.dll`, + `C:\Program Files (x86)\1Password\app\8\op_sdk_ipc_client.dll`, + path.Join(home, `AppData\Local\1Password\app\8\op_sdk_ipc_client.dll`), + } + + default: + return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS) + } + for _, libPath := range locations { + if _, err := os.Stat(libPath); err == nil { + return libPath, nil + } + } + + return "", fmt.Errorf("1Password desktop application not found") +} + +func GetSharedLibCore(accountName string) (*CoreWrapper, error) { + if coreLib == nil { + libPath, err := find1PasswordLibPath() + if err != nil { + return nil, err + } + coreLib, err = loadCore(libPath) + if err != nil { + return nil, err + } + coreLib.accountName = accountName + } + + coreWrapper := CoreWrapper{InnerCore: coreLib} + + return &coreWrapper, nil +} + +// InitClient creates a client instance in the current core module and returns its unique ID. +func (slc *SharedLibCore) InitClient(ctx context.Context, config []byte) ([]byte, error) { + const kind = "init_client" + request := Request{ + Kind: kind, + AccountName: slc.accountName, + Payload: config, + } + + requestMarshaled, err := json.Marshal(request) + if err != nil { + return nil, err + } + res, err := slc.callSharedLibrary(requestMarshaled) + if err != nil { + return nil, err + } + + return res, nil +} + +// Invoke performs an SDK operation. +func (slc *SharedLibCore) Invoke(ctx context.Context, invokeConfig []byte) ([]byte, error) { + const kind = "invoke" + request := Request{ + Kind: kind, + AccountName: slc.accountName, + Payload: invokeConfig, + } + + requestMarshaled, err := json.Marshal(request) + if err != nil { + return nil, err + } + + res, err := slc.callSharedLibrary(requestMarshaled) + if err != nil { + return nil, err + } + + return res, nil +} + +// ReleaseClient releases memory in the core associated with the given client ID. +func (slc *SharedLibCore) ReleaseClient(clientID []byte) { + _, err := slc.callSharedLibrary(clientID) + if err != nil { + log.Println("failed to release client") + } +} diff --git a/internal/shared_lib_core_unix.go b/internal/shared_lib_core_unix.go index 497132b..9f10400 100644 --- a/internal/shared_lib_core_unix.go +++ b/internal/shared_lib_core_unix.go @@ -3,14 +3,9 @@ package internal import ( - "context" "encoding/json" "errors" "fmt" - "log" - "os" - "path" - "runtime" "unsafe" ) @@ -85,60 +80,6 @@ type SharedLibCore struct { var coreLib *SharedLibCore -// find1PasswordLibPath returns the path to the 1Password shared library -// (libop_sdk_ipc_client.dylib/.so/.dll) depending on OS. -func find1PasswordLibPath() (string, error) { - var locations []string - - home, err := os.UserHomeDir() - if err != nil { - return "", err - } - - switch runtime.GOOS { - case "darwin": - locations = []string{ - "/Applications/1Password.app/Contents/Frameworks/libop_sdk_ipc_client.dylib", - path.Join(home, "Applications/1Password.app/Contents/Frameworks/libop_sdk_ipc_client.dylib"), - } - - case "linux": - locations = []string{ - "/usr/bin/1password/libop_sdk_ipc_client.so", - "/opt/1Password/libop_sdk_ipc_client.so", - "/snap/bin/1password/libop_sdk_ipc_client.so", - } - - default: - return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS) - } - for _, libPath := range locations { - if _, err := os.Stat(libPath); err == nil { - return libPath, nil - } - } - - return "", fmt.Errorf("1Password desktop application not found") -} - -func GetSharedLibCore(accountName string) (*CoreWrapper, error) { - if coreLib == nil { - path, err := find1PasswordLibPath() - if err != nil { - return nil, err - } - coreLib, err = loadCore(path) - if err != nil { - return nil, err - } - coreLib.accountName = accountName - } - - coreWrapper := CoreWrapper{InnerCore: coreLib} - - return &coreWrapper, nil -} - func loadCore(path string) (*SharedLibCore, error) { cPath := C.CString(path) defer C.free(unsafe.Pointer(cPath)) @@ -173,56 +114,6 @@ func loadCore(path string) (*SharedLibCore, error) { }, nil } -// InitClient creates a client instance in the current core module and returns its unique ID. -func (slc *SharedLibCore) InitClient(ctx context.Context, config []byte) ([]byte, error) { - const kind = "init_client" - request := Request{ - Kind: kind, - AccountName: slc.accountName, - Payload: config, - } - - requestMarshaled, err := json.Marshal(request) - if err != nil { - return nil, err - } - res, err := slc.callSharedLibrary(requestMarshaled) - if err != nil { - return nil, err - } - - return res, nil -} - -func (slc *SharedLibCore) Invoke(ctx context.Context, invokeConfig []byte) ([]byte, error) { - const kind = "invoke" - request := Request{ - Kind: kind, - AccountName: slc.accountName, - Payload: invokeConfig, - } - - requestMarshaled, err := json.Marshal(request) - if err != nil { - return nil, err - } - - res, err := slc.callSharedLibrary(requestMarshaled) - if err != nil { - return nil, err - } - - return res, nil -} - -// ReleaseClient releases memory in the core associated with the given client ID. -func (slc *SharedLibCore) ReleaseClient(clientID []byte) { - _, err := slc.callSharedLibrary(clientID) - if err != nil { - log.Println("failed to release client") - } -} - func (slc *SharedLibCore) callSharedLibrary(input []byte) ([]byte, error) { if len(input) == 0 { return nil, errors.New("internal: empty input") @@ -261,18 +152,3 @@ func (slc *SharedLibCore) callSharedLibrary(input []byte) ([]byte, error) { return nil, response } } - -type Request struct { - Kind string `json:"kind"` - AccountName string `json:"account_name"` - Payload []byte `json:"payload"` -} - -type Response struct { - Success bool `json:"success"` - Payload []byte `json:"payload"` -} - -func (r Response) Error() string { - return string(r.Payload) -} diff --git a/internal/shared_lib_core_windows.go b/internal/shared_lib_core_windows.go index 65f2187..941dd84 100644 --- a/internal/shared_lib_core_windows.go +++ b/internal/shared_lib_core_windows.go @@ -3,13 +3,9 @@ package internal import ( - "context" "encoding/json" "errors" "fmt" - "log" - "os" - "path" "unsafe" "golang.org/x/sys/windows" @@ -24,58 +20,6 @@ type SharedLibCore struct { var coreLib *SharedLibCore -// Request/Response mirror your Unix file (kept identical) -type Request struct { - Kind string `json:"kind"` - AccountName string `json:"account_name"` - Payload []byte `json:"payload"` -} - -type Response struct { - Success bool `json:"success"` - Payload []byte `json:"payload"` -} - -func (r Response) Error() string { return string(r.Payload) } - -// find1PasswordLibPath returns the path to the DLL on Windows -func find1PasswordLibPath() (string, error) { - home, err := os.UserHomeDir() - if err != nil { - return "", err - } - - locations := []string{ - path.Join(home, `AppData\Local\1Password\op_sdk_ipc_client.dll`), - `C:\Program Files\1Password\app\8\op_sdk_ipc_client.dll`, - `C:\Program Files (x86)\1Password\app\8\op_sdk_ipc_client.dll`, - path.Join(home, `AppData\Local\1Password\app\8\op_sdk_ipc_client.dll`), - } - for _, p := range locations { - if _, err := os.Stat(p); err == nil { - return p, nil - } - } - return "", fmt.Errorf("1Password desktop application not found") -} - -// API identical to Unix version -func GetSharedLibCore(accountName string) (*CoreWrapper, error) { - if coreLib == nil { - path, err := find1PasswordLibPath() - if err != nil { - return nil, err - } - coreLib, err = loadCore(path) - if err != nil { - return nil, err - } - coreLib.accountName = accountName - } - coreWrapper := CoreWrapper{InnerCore: coreLib} - return &coreWrapper, nil -} - func loadCore(path string) (*SharedLibCore, error) { dll, err := windows.LoadDLL(path) // absolute path avoids search path surprises if err != nil { @@ -98,49 +42,12 @@ func loadCore(path string) (*SharedLibCore, error) { }, nil } -// InitClient creates a client instance in the current core module and returns its unique ID. -func (slc *SharedLibCore) InitClient(ctx context.Context, config []byte) ([]byte, error) { - const kind = "init_client" - req := Request{ - Kind: kind, - AccountName: slc.accountName, - Payload: config, - } - input, err := json.Marshal(req) - if err != nil { - return nil, err - } - return slc.callSharedLibrary(input) -} - -func (slc *SharedLibCore) Invoke(ctx context.Context, invokeConfig []byte) ([]byte, error) { - const kind = "invoke" - req := Request{ - Kind: kind, - AccountName: slc.accountName, - Payload: invokeConfig, - } - input, err := json.Marshal(req) - if err != nil { - return nil, err - } - return slc.callSharedLibrary(input) -} - -// ReleaseClient releases memory in the core associated with the given client ID. -func (slc *SharedLibCore) ReleaseClient(clientID []byte) { - _, err := slc.callSharedLibrary(clientID) - if err != nil { - log.Println("failed to release client") - } -} - func (slc *SharedLibCore) callSharedLibrary(input []byte) ([]byte, error) { if len(input) == 0 { return nil, errors.New("internal: empty input") } - // Signature we’re calling (from your Rust exports): + // Signature we’re calling (from Rust exports): // int32_t op_sdk_ipc_send_message(const uint8_t* msg_ptr, size_t msg_len, // uint8_t** out_buf, size_t* out_len, size_t* out_cap); var outBuf *byte @@ -154,7 +61,7 @@ func (slc *SharedLibCore) callSharedLibrary(input []byte) ([]byte, error) { uintptr(unsafe.Pointer(&outLen)), uintptr(unsafe.Pointer(&outCap)), ) - // syscall layer error? + // syscall layer error if callErr != nil && callErr != windows.ERROR_SUCCESS { return nil, callErr }