...
1
2
3 package osxkeychain
4
5
12 import "C"
13
14 import (
15 "errors"
16 "strconv"
17 "unsafe"
18
19 "github.com/docker/docker-credential-helpers/credentials"
20 "github.com/docker/docker-credential-helpers/registryurl"
21 )
22
23
24
25 const errCredentialsNotFound = "The specified item could not be found in the keychain."
26
27
28
29 const errInteractionNotAllowed = "User interaction is not allowed."
30
31
32 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`)
33
34
35 type Osxkeychain struct{}
36
37
38 func (h Osxkeychain) Add(creds *credentials.Credentials) error {
39 _ = h.Delete(creds.ServerURL)
40
41 s, err := splitServer(creds.ServerURL)
42 if err != nil {
43 return err
44 }
45 defer freeServer(s)
46
47 label := C.CString(credentials.CredsLabel)
48 defer C.free(unsafe.Pointer(label))
49 username := C.CString(creds.Username)
50 defer C.free(unsafe.Pointer(username))
51 secret := C.CString(creds.Secret)
52 defer C.free(unsafe.Pointer(secret))
53
54 errMsg := C.keychain_add(s, label, username, secret)
55 if errMsg != nil {
56 defer C.free(unsafe.Pointer(errMsg))
57 return errors.New(C.GoString(errMsg))
58 }
59
60 return nil
61 }
62
63
64 func (h Osxkeychain) Delete(serverURL string) error {
65 s, err := splitServer(serverURL)
66 if err != nil {
67 return err
68 }
69 defer freeServer(s)
70
71 if errMsg := C.keychain_delete(s); errMsg != nil {
72 defer C.free(unsafe.Pointer(errMsg))
73 switch goMsg := C.GoString(errMsg); goMsg {
74 case errCredentialsNotFound:
75 return credentials.NewErrCredentialsNotFound()
76 case errInteractionNotAllowed:
77 return ErrInteractionNotAllowed
78 default:
79 return errors.New(goMsg)
80 }
81 }
82
83 return nil
84 }
85
86
87 func (h Osxkeychain) Get(serverURL string) (string, string, error) {
88 s, err := splitServer(serverURL)
89 if err != nil {
90 return "", "", err
91 }
92 defer freeServer(s)
93
94 var usernameLen C.uint
95 var username *C.char
96 var secretLen C.uint
97 var secret *C.char
98 defer C.free(unsafe.Pointer(username))
99 defer C.free(unsafe.Pointer(secret))
100
101 errMsg := C.keychain_get(s, &usernameLen, &username, &secretLen, &secret)
102 if errMsg != nil {
103 defer C.free(unsafe.Pointer(errMsg))
104 switch goMsg := C.GoString(errMsg); goMsg {
105 case errCredentialsNotFound:
106 return "", "", credentials.NewErrCredentialsNotFound()
107 case errInteractionNotAllowed:
108 return "", "", ErrInteractionNotAllowed
109 default:
110 return "", "", errors.New(goMsg)
111 }
112 }
113
114 user := C.GoStringN(username, C.int(usernameLen))
115 pass := C.GoStringN(secret, C.int(secretLen))
116 return user, pass, nil
117 }
118
119
120 func (h Osxkeychain) List() (map[string]string, error) {
121 credsLabelC := C.CString(credentials.CredsLabel)
122 defer C.free(unsafe.Pointer(credsLabelC))
123
124 var pathsC **C.char
125 defer C.free(unsafe.Pointer(pathsC))
126 var acctsC **C.char
127 defer C.free(unsafe.Pointer(acctsC))
128 var listLenC C.uint
129 errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC)
130 defer C.freeListData(&pathsC, listLenC)
131 defer C.freeListData(&acctsC, listLenC)
132 if errMsg != nil {
133 defer C.free(unsafe.Pointer(errMsg))
134 switch goMsg := C.GoString(errMsg); goMsg {
135 case errCredentialsNotFound:
136 return make(map[string]string), nil
137 case errInteractionNotAllowed:
138 return nil, ErrInteractionNotAllowed
139 default:
140 return nil, errors.New(goMsg)
141 }
142 }
143
144 var listLen int
145 listLen = int(listLenC)
146 pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
147 acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
148
149 resp := make(map[string]string)
150 for i := 0; i < listLen; i++ {
151 if C.GoString(pathTmp[i]) == "0" {
152 continue
153 }
154 resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
155 }
156 return resp, nil
157 }
158
159 func splitServer(serverURL string) (*C.struct_Server, error) {
160 u, err := registryurl.Parse(serverURL)
161 if err != nil {
162 return nil, err
163 }
164
165 proto := C.kSecProtocolTypeHTTPS
166 if u.Scheme == "http" {
167 proto = C.kSecProtocolTypeHTTP
168 }
169 var port int
170 p := u.Port()
171 if p != "" {
172 port, err = strconv.Atoi(p)
173 if err != nil {
174 return nil, err
175 }
176 }
177
178 return &C.struct_Server{
179 proto: C.SecProtocolType(proto),
180 host: C.CString(u.Hostname()),
181 port: C.uint(port),
182 path: C.CString(u.Path),
183 }, nil
184 }
185
186 func freeServer(s *C.struct_Server) {
187 C.free(unsafe.Pointer(s.host))
188 C.free(unsafe.Pointer(s.path))
189 }
190
View as plain text