1
0
mirror of https://github.com/docker/docker-credential-helpers.git synced 2026-06-13 16:01:28 +05:30

osxkeychain: switch to github.com/keybase/go-keychain

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2023-05-27 23:18:54 +02:00
committed by CrazyMax
parent 8438667191
commit 4cdcdc29eb
16 changed files with 1438 additions and 344 deletions
+64 -100
View File
@@ -3,30 +3,31 @@
package osxkeychain
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Security -framework Foundation
#cgo LDFLAGS: -framework Security -framework CoreFoundation
#include "osxkeychain.h"
#include <stdlib.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
*/
import "C"
import (
"errors"
"strconv"
"unsafe"
"github.com/docker/docker-credential-helpers/credentials"
"github.com/docker/docker-credential-helpers/registryurl"
"github.com/keybase/go-keychain"
)
// errCredentialsNotFound is the specific error message returned by OS X
// when the credentials are not in the keychain.
const errCredentialsNotFound = "The specified item could not be found in the keychain."
// errCredentialsNotFound is the specific error message returned by OS X
// when environment does not allow showing dialog to unlock keychain.
const errInteractionNotAllowed = "User interaction is not allowed."
// https://opensource.apple.com/source/Security/Security-55471/sec/Security/SecBase.h.auto.html
const (
// errCredentialsNotFound is the specific error message returned by OS X
// when the credentials are not in the keychain.
errCredentialsNotFound = "The specified item could not be found in the keychain. (-25300)"
// errInteractionNotAllowed is the specific error message returned by OS X
// when environment does not allow showing dialog to unlock keychain.
errInteractionNotAllowed = "User interaction is not allowed. (-25308)"
)
// ErrInteractionNotAllowed is returned if keychain password prompt can not be shown.
var ErrInteractionNotAllowed = errors.New(`keychain cannot be accessed because the current session does not allow user interaction. The keychain may be locked; unlock it by running "security -v unlock-keychain ~/Library/Keychains/login.keychain-db" and try again`)
@@ -38,152 +39,115 @@ type Osxkeychain struct{}
func (h Osxkeychain) Add(creds *credentials.Credentials) error {
_ = h.Delete(creds.ServerURL) // ignore errors as existing credential may not exist.
s, err := splitServer(creds.ServerURL)
if err != nil {
item := keychain.NewItem()
item.SetSecClass(keychain.SecClassInternetPassword)
item.SetLabel(credentials.CredsLabel)
item.SetAccount(creds.Username)
item.SetData([]byte(creds.Secret))
if err := splitServer(creds.ServerURL, item); err != nil {
return err
}
defer freeServer(s)
label := C.CString(credentials.CredsLabel)
defer C.free(unsafe.Pointer(label))
username := C.CString(creds.Username)
defer C.free(unsafe.Pointer(username))
secret := C.CString(creds.Secret)
defer C.free(unsafe.Pointer(secret))
errMsg := C.keychain_add(s, label, username, secret)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
return errors.New(C.GoString(errMsg))
}
return nil
return keychain.AddItem(item)
}
// Delete removes credentials from the keychain.
func (h Osxkeychain) Delete(serverURL string) error {
s, err := splitServer(serverURL)
if err != nil {
item := keychain.NewItem()
item.SetSecClass(keychain.SecClassInternetPassword)
if err := splitServer(serverURL, item); err != nil {
return err
}
defer freeServer(s)
if errMsg := C.keychain_delete(s); errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
switch goMsg := C.GoString(errMsg); goMsg {
if err := keychain.DeleteItem(item); err != nil {
switch err.Error() {
case errCredentialsNotFound:
return credentials.NewErrCredentialsNotFound()
case errInteractionNotAllowed:
return ErrInteractionNotAllowed
default:
return errors.New(goMsg)
return err
}
}
return nil
}
// Get returns the username and secret to use for a given registry server URL.
func (h Osxkeychain) Get(serverURL string) (string, string, error) {
s, err := splitServer(serverURL)
if err != nil {
item := keychain.NewItem()
item.SetSecClass(keychain.SecClassInternetPassword)
item.SetMatchLimit(keychain.MatchLimitOne)
item.SetReturnAttributes(true)
item.SetReturnData(true)
if err := splitServer(serverURL, item); err != nil {
return "", "", err
}
defer freeServer(s)
var usernameLen C.uint
var username *C.char
var secretLen C.uint
var secret *C.char
defer C.free(unsafe.Pointer(username))
defer C.free(unsafe.Pointer(secret))
errMsg := C.keychain_get(s, &usernameLen, &username, &secretLen, &secret)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
switch goMsg := C.GoString(errMsg); goMsg {
res, err := keychain.QueryItem(item)
if err != nil {
switch err.Error() {
case errCredentialsNotFound:
return "", "", credentials.NewErrCredentialsNotFound()
case errInteractionNotAllowed:
return "", "", ErrInteractionNotAllowed
default:
return "", "", errors.New(goMsg)
return "", "", err
}
} else if len(res) == 0 {
return "", "", credentials.NewErrCredentialsNotFound()
}
user := C.GoStringN(username, C.int(usernameLen))
pass := C.GoStringN(secret, C.int(secretLen))
return user, pass, nil
return res[0].Account, string(res[0].Data), nil
}
// List returns the stored URLs and corresponding usernames.
func (h Osxkeychain) List() (map[string]string, error) {
credsLabelC := C.CString(credentials.CredsLabel)
defer C.free(unsafe.Pointer(credsLabelC))
item := keychain.NewItem()
item.SetSecClass(keychain.SecClassInternetPassword)
item.SetMatchLimit(keychain.MatchLimitAll)
item.SetReturnAttributes(true)
item.SetLabel(credentials.CredsLabel)
var pathsC **C.char
defer C.free(unsafe.Pointer(pathsC))
var acctsC **C.char
defer C.free(unsafe.Pointer(acctsC))
var listLenC C.uint
errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC)
defer C.freeListData(&pathsC, listLenC)
defer C.freeListData(&acctsC, listLenC)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
switch goMsg := C.GoString(errMsg); goMsg {
res, err := keychain.QueryItem(item)
if err != nil {
switch err.Error() {
case errCredentialsNotFound:
return make(map[string]string), nil
case errInteractionNotAllowed:
return nil, ErrInteractionNotAllowed
default:
return nil, errors.New(goMsg)
return nil, err
}
} else if len(res) == 0 {
return nil, credentials.NewErrCredentialsNotFound()
}
var listLen int
listLen = int(listLenC)
pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
// taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper
resp := make(map[string]string)
for i := 0; i < listLen; i++ {
if C.GoString(pathTmp[i]) == "0" {
for _, r := range res {
if r.Path == "" {
continue
}
resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
resp[r.Path] = r.Account
}
return resp, nil
}
func splitServer(serverURL string) (*C.struct_Server, error) {
func splitServer(serverURL string, item keychain.Item) error {
u, err := registryurl.Parse(serverURL)
if err != nil {
return nil, err
return err
}
proto := C.kSecProtocolTypeHTTPS
item.SetProtocol("https")
if u.Scheme == "http" {
proto = C.kSecProtocolTypeHTTP
item.SetProtocol("http")
}
var port int
p := u.Port()
if p != "" {
port, err = strconv.Atoi(p)
item.SetServer(u.Hostname())
if p := u.Port(); p != "" {
port, err := strconv.Atoi(p)
if err != nil {
return nil, err
return err
}
item.SetPort(int32(port))
}
return &C.struct_Server{
proto: C.SecProtocolType(proto),
host: C.CString(u.Hostname()),
port: C.uint(port),
path: C.CString(u.Path),
}, nil
}
func freeServer(s *C.struct_Server) {
C.free(unsafe.Pointer(s.host))
C.free(unsafe.Pointer(s.path))
item.SetPath(u.Path)
return nil
}