Skip to content

Commit 5a8fb21

Browse files
committed
Full implementation for OSX ready
Signed-off-by: avaid96 <[email protected]>
1 parent 5128fa1 commit 5a8fb21

File tree

7 files changed

+170
-3
lines changed

7 files changed

+170
-3
lines changed

credentials/credentials.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ type Credentials struct {
1717
Secret string
1818
}
1919

20+
type KeyData struct{
21+
Path string
22+
Username string
23+
}
24+
2025
// Serve initializes the credentials helper and parses the action argument.
2126
// This function is designed to be called from a command line interface.
2227
// It uses os.Args[1] as the key for the action.
@@ -127,3 +132,26 @@ func Erase(helper Helper, reader io.Reader) error {
127132

128133
return helper.Delete(serverURL)
129134
}
135+
136+
//List returns all the serverURLs of keys in
137+
//the OS store as a list of strings
138+
func List(helper Helper, writer io.Writer) error {
139+
x, y, err := helper.List()
140+
if err != nil {
141+
return err
142+
}
143+
keyDataList := []KeyData{}
144+
for index, _ := range(x) {
145+
keyDataObj := KeyData{
146+
Path:x[index],
147+
Username:y[index],
148+
}
149+
keyDataList = append([]KeyData{keyDataObj}, keyDataList...)
150+
}
151+
buffer := new(bytes.Buffer)
152+
if err := json.NewEncoder(buffer).Encode(keyDataList); err != nil {
153+
return err
154+
}
155+
fmt.Fprint(writer, buffer.String())
156+
return nil
157+
}

credentials/credentials_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ func (m *memoryStore) Get(serverURL string) (string, string, error) {
3636
return c.Username, c.Secret, nil
3737
}
3838

39+
func (m *memoryStore) List() ([]string, []string, error) {
40+
//Simply a placeholder to let memoryStore be a valid implementation of Helper interface
41+
return nil, nil, nil
42+
}
43+
3944
func TestStore(t *testing.T) {
4045
serverURL := "https://index.docker.io/v1/"
4146
creds := &Credentials{
@@ -138,3 +143,16 @@ func TestErase(t *testing.T) {
138143
t.Fatal("expected error getting missing creds, got empty")
139144
}
140145
}
146+
147+
func TestList(t *testing.T) {
148+
//This tests that there is proper input an output into the byte stream
149+
//Individual stores are very OS specific and have been tested in osxkeychain and secretservice respectively
150+
out := new(bytes.Buffer)
151+
h := newMemoryStore()
152+
if err := List(h, out); err != nil {
153+
t.Fatal(err)
154+
}
155+
if out.Len() == 0 {
156+
t.Fatalf("expected output in the writer, got %d", 0)
157+
}
158+
}

credentials/helper.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ type Helper interface {
99
// Get retrieves credentials from the store.
1010
// It returns username and secret as strings.
1111
Get(serverURL string) (string, string, error)
12+
// List returns all the serverURLs of keys in
13+
// the OS store as a list of strings
14+
List() ([]string, []string, error)
1215
}

osxkeychain/osxkeychain_darwin.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#include "osxkeychain_darwin.h"
2+
#include <CoreFoundation/CoreFoundation.h>
3+
#include <stdio.h>
4+
#include <string.h>
25

36
char *get_error(OSStatus status) {
47
char *buf = malloc(128);
@@ -96,3 +99,77 @@ char *keychain_delete(struct Server *server) {
9699
}
97100
return NULL;
98101
}
102+
103+
char * CFStringToCharArr(CFStringRef aString) {
104+
if (aString == NULL) {
105+
return NULL;
106+
}
107+
CFIndex length = CFStringGetLength(aString);
108+
CFIndex maxSize =
109+
CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
110+
char *buffer = (char *)malloc(maxSize);
111+
if (CFStringGetCString(aString, buffer, maxSize,
112+
kCFStringEncodingUTF8)) {
113+
return buffer;
114+
}
115+
return NULL;
116+
}
117+
118+
char *keychain_list(char *** paths, char *** accts, unsigned int *list_l) {
119+
CFMutableDictionaryRef query = CFDictionaryCreateMutable (NULL, 1, NULL, NULL);
120+
CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
121+
CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue);
122+
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
123+
//Use this query dictionary
124+
CFTypeRef result= NULL;
125+
OSStatus status = SecItemCopyMatching(
126+
query,
127+
&result);
128+
//Ran a search and store the results in result
129+
if (status) {
130+
return get_error(status);
131+
}
132+
int numKeys = CFArrayGetCount(result);
133+
*paths = (char **) malloc((int)sizeof(char *)*numKeys);
134+
*accts = (char **) malloc((int)sizeof(char *)*numKeys);
135+
//result is of type CFArray
136+
for(int i=0; i<numKeys; i++) {
137+
CFDictionaryRef currKey = CFArrayGetValueAtIndex(result,i);
138+
if (CFDictionaryContainsKey(currKey, CFSTR("path"))) {
139+
//Even if a key is stored without an account, Apple defaults it to null so these arrays will be of the same length
140+
CFStringRef pathTmp = CFDictionaryGetValue(currKey, CFSTR("path"));
141+
CFStringRef acctTmp = CFDictionaryGetValue(currKey, CFSTR("acct"));
142+
if (acctTmp == NULL) {
143+
acctTmp = CFSTR("<unknown>");
144+
}
145+
char * path = (char *) malloc(CFStringGetLength(pathTmp)+1);
146+
path = CFStringToCharArr(pathTmp);
147+
path[strlen(path)] = '\0';
148+
char * acct = (char *) malloc(CFStringGetLength(acctTmp)+1); //<- problem line in 38th iteration
149+
acct = CFStringToCharArr(acctTmp);
150+
acct[strlen(acct)] = '\0';
151+
//We now have all we need, username and servername. Now export this to .go
152+
(*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)+1));
153+
memcpy((*paths)[i], path, sizeof(char)*(strlen(path)+1));
154+
(*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)+1));
155+
memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)+1));
156+
}
157+
else {
158+
char * path = "0";
159+
char * acct = "0";
160+
(*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)));
161+
memcpy((*paths)[i], path, sizeof(char)*(strlen(path)));
162+
(*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)));
163+
memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)));
164+
}
165+
}
166+
*list_l = numKeys;
167+
return NULL;
168+
}
169+
170+
void freeListData(char *** data, unsigned int length) {
171+
for(int i=0; i<length; i++) {
172+
free((*data)[i]);
173+
}
174+
free(*data);
175+
}

osxkeychain/osxkeychain_darwin.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"strconv"
1515
"strings"
1616
"unsafe"
17-
1817
"github.com/docker/docker-credential-helpers/credentials"
1918
)
2019

@@ -83,7 +82,6 @@ func (h Osxkeychain) Get(serverURL string) (string, string, error) {
8382
if errMsg != nil {
8483
defer C.free(unsafe.Pointer(errMsg))
8584
goMsg := C.GoString(errMsg)
86-
8785
if goMsg == errCredentialsNotFound {
8886
return "", "", credentials.NewErrCredentialsNotFound()
8987
}
@@ -96,6 +94,42 @@ func (h Osxkeychain) Get(serverURL string) (string, string, error) {
9694
return user, pass, nil
9795
}
9896

97+
func (h Osxkeychain) List() ([]string, []string, error){
98+
var pathsC** C.char
99+
defer C.free(unsafe.Pointer(pathsC))
100+
var acctsC** C.char
101+
defer C.free(unsafe.Pointer(acctsC))
102+
var listLenC C.uint
103+
errMsg := C.keychain_list(&pathsC, &acctsC, &listLenC)
104+
if errMsg!=nil {
105+
defer C.free(unsafe.Pointer(errMsg))
106+
goMsg := C.GoString(errMsg)
107+
return nil, nil, errors.New(goMsg)
108+
}
109+
var listLen int;
110+
listLen = int(listLenC)
111+
pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
112+
acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
113+
//taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper
114+
paths := make([]string, listLen)
115+
accts := make([]string, listLen)
116+
at := 0
117+
for i := 0; i < listLen ; i++ {
118+
if C.GoString(pathTmp[i])=="0" {
119+
continue
120+
}
121+
paths[at] = C.GoString(pathTmp[i])
122+
accts[at] = C.GoString(acctTmp[i])
123+
at = at + 1
124+
}
125+
paths = paths[:at]
126+
accts = accts[:at]
127+
//still need to free all the memory we allocated in the c file
128+
//do it here >>
129+
C.freeListData(&pathsC, listLenC)
130+
return paths, accts, nil
131+
}
132+
99133
func splitServer(serverURL string) (*C.struct_Server, error) {
100134
u, err := url.Parse(serverURL)
101135
if err != nil {
@@ -130,3 +164,4 @@ func freeServer(s *C.struct_Server) {
130164
C.free(unsafe.Pointer(s.host))
131165
C.free(unsafe.Pointer(s.path))
132166
}
167+

osxkeychain/osxkeychain_darwin.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ struct Server {
1010
char *keychain_add(struct Server *server, char *username, char *secret);
1111
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret);
1212
char *keychain_delete(struct Server *server);
13+
char *keychain_list(char *** data, char *** accts, unsigned int *list_l);
14+
void freeListData(char *** data, unsigned int length);

osxkeychain/osxkeychain_darwin_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package osxkeychain
22

33
import (
44
"testing"
5-
65
"github.com/docker/docker-credential-helpers/credentials"
76
)
87

@@ -34,6 +33,11 @@ func TestOSXKeychainHelper(t *testing.T) {
3433
if err := helper.Delete(creds.ServerURL); err != nil {
3534
t.Fatal(err)
3635
}
36+
37+
_, _, err = helper.List();
38+
if err != nil {
39+
t.Fatal(err)
40+
}
3741
}
3842

3943
func TestMissingCredentials(t *testing.T) {

0 commit comments

Comments
 (0)