| //go:build linux && cgo |
| |
| package secretservice |
| |
| /* |
| #cgo pkg-config: libsecret-1 |
| |
| #include "secretservice.h" |
| #include <stdlib.h> |
| */ |
| import "C" |
| |
| import ( |
| "errors" |
| "unsafe" |
| |
| "github.com/docker/docker-credential-helpers/credentials" |
| ) |
| |
| // Secretservice handles secrets using Linux secret-service as a store. |
| type Secretservice struct{} |
| |
| // Add adds new credentials to the keychain. |
| func (h Secretservice) Add(creds *credentials.Credentials) error { |
| if creds == nil { |
| return errors.New("missing credentials") |
| } |
| credsLabel := C.CString(credentials.CredsLabel) |
| defer C.free(unsafe.Pointer(credsLabel)) |
| server := C.CString(creds.ServerURL) |
| defer C.free(unsafe.Pointer(server)) |
| username := C.CString(creds.Username) |
| defer C.free(unsafe.Pointer(username)) |
| secret := C.CString(creds.Secret) |
| defer C.free(unsafe.Pointer(secret)) |
| displayLabel := C.CString("Registry credentials for " + creds.ServerURL) |
| defer C.free(unsafe.Pointer(displayLabel)) |
| |
| if err := C.add(credsLabel, server, username, secret, displayLabel); err != nil { |
| defer C.g_error_free(err) |
| errMsg := (*C.char)(unsafe.Pointer(err.message)) |
| return errors.New(C.GoString(errMsg)) |
| } |
| return nil |
| } |
| |
| // Delete removes credentials from the store. |
| func (h Secretservice) Delete(serverURL string) error { |
| if serverURL == "" { |
| return errors.New("missing server url") |
| } |
| server := C.CString(serverURL) |
| defer C.free(unsafe.Pointer(server)) |
| |
| if err := C.delete(server); err != nil { |
| defer C.g_error_free(err) |
| errMsg := (*C.char)(unsafe.Pointer(err.message)) |
| return errors.New(C.GoString(errMsg)) |
| } |
| return nil |
| } |
| |
| // Get returns the username and secret to use for a given registry server URL. |
| func (h Secretservice) Get(serverURL string) (string, string, error) { |
| if serverURL == "" { |
| return "", "", errors.New("missing server url") |
| } |
| var username *C.char |
| defer C.free(unsafe.Pointer(username)) |
| var secret *C.char |
| defer C.free(unsafe.Pointer(secret)) |
| server := C.CString(serverURL) |
| defer C.free(unsafe.Pointer(server)) |
| |
| err := C.get(server, &username, &secret) |
| if err != nil { |
| defer C.g_error_free(err) |
| errMsg := (*C.char)(unsafe.Pointer(err.message)) |
| return "", "", errors.New(C.GoString(errMsg)) |
| } |
| user := C.GoString(username) |
| pass := C.GoString(secret) |
| if pass == "" { |
| return "", "", credentials.NewErrCredentialsNotFound() |
| } |
| return user, pass, nil |
| } |
| |
| // List returns the stored URLs and corresponding usernames for a given credentials label |
| func (h Secretservice) List() (map[string]string, error) { |
| credsLabelC := C.CString(credentials.CredsLabel) |
| defer C.free(unsafe.Pointer(credsLabelC)) |
| |
| var pathsC **C.char |
| defer C.free(unsafe.Pointer(pathsC)) |
| var acctsC **C.char |
| defer C.free(unsafe.Pointer(acctsC)) |
| var listLenC C.uint |
| err := C.list(credsLabelC, &pathsC, &acctsC, &listLenC) |
| defer C.freeListData(&pathsC, listLenC) |
| defer C.freeListData(&acctsC, listLenC) |
| if err != nil { |
| defer C.g_error_free(err) |
| errMsg := (*C.char)(unsafe.Pointer(err.message)) |
| return nil, errors.New(C.GoString(errMsg)) |
| } |
| |
| resp := make(map[string]string) |
| |
| listLen := int(listLenC) |
| if listLen == 0 { |
| return resp, nil |
| } |
| // The maximum capacity of the following two slices is limited to (2^29)-1 to remain compatible |
| // with 32-bit platforms. The size of a `*C.char` (a pointer) is 4 Byte on a 32-bit system |
| // and (2^29)*4 == math.MaxInt32 + 1. -- See issue golang/go#13656 |
| pathTmp := (*[(1 << 29) - 1]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen] |
| acctTmp := (*[(1 << 29) - 1]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen] |
| for i := 0; i < listLen; i++ { |
| resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i]) |
| } |
| |
| return resp, nil |
| } |