1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
|
package osxkeychain
/*
#cgo CFLAGS: -x objective-c -mmacosx-version-min=10.11
#cgo LDFLAGS: -framework Security -framework Foundation -mmacosx-version-min=10.11
#include "osxkeychain_darwin.h"
#include <stdlib.h>
*/
import "C"
import (
"errors"
"strconv"
"unsafe"
"github.com/docker/docker-credential-helpers/credentials"
"github.com/docker/docker-credential-helpers/registryurl"
)
// 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."
// 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`)
// Osxkeychain handles secrets using the OS X Keychain as store.
type Osxkeychain struct{}
// Add adds new credentials to the keychain.
func (h Osxkeychain) Add(creds *credentials.Credentials) error {
h.Delete(creds.ServerURL)
s, err := splitServer(creds.ServerURL)
if 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
}
// Delete removes credentials from the keychain.
func (h Osxkeychain) Delete(serverURL string) error {
s, err := splitServer(serverURL)
if err != nil {
return err
}
defer freeServer(s)
errMsg := C.keychain_delete(s)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
return errors.New(C.GoString(errMsg))
}
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 {
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))
goMsg := C.GoString(errMsg)
if goMsg == errCredentialsNotFound {
return "", "", credentials.NewErrCredentialsNotFound()
}
if goMsg == errInteractionNotAllowed {
return "", "", ErrInteractionNotAllowed
}
return "", "", errors.New(goMsg)
}
user := C.GoStringN(username, C.int(usernameLen))
pass := C.GoStringN(secret, C.int(secretLen))
return user, pass, 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))
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))
goMsg := C.GoString(errMsg)
if goMsg == errCredentialsNotFound {
return make(map[string]string), nil
}
if goMsg == errInteractionNotAllowed {
return nil, ErrInteractionNotAllowed
}
return nil, errors.New(goMsg)
}
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" {
continue
}
resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
}
return resp, nil
}
func splitServer(serverURL string) (*C.struct_Server, error) {
u, err := registryurl.Parse(serverURL)
if err != nil {
return nil, err
}
proto := C.kSecProtocolTypeHTTPS
if u.Scheme == "http" {
proto = C.kSecProtocolTypeHTTP
}
var port int
p := registryurl.GetPort(u)
if p != "" {
port, err = strconv.Atoi(p)
if err != nil {
return nil, err
}
}
return &C.struct_Server{
proto: C.SecProtocolType(proto),
host: C.CString(registryurl.GetHostname(u)),
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))
}
|