From 5a8fb214ed22caee8e035cc38ffc66e46d67e0b2 Mon Sep 17 00:00:00 2001 From: avaid96 Date: Mon, 11 Jul 2016 10:50:44 -0700 Subject: [PATCH] Full implementation for OSX ready Signed-off-by: avaid96 --- credentials/credentials.go | 28 ++++++++++ credentials/credentials_test.go | 18 ++++++ credentials/helper.go | 3 + osxkeychain/osxkeychain_darwin.c | 77 ++++++++++++++++++++++++++ osxkeychain/osxkeychain_darwin.go | 39 ++++++++++++- osxkeychain/osxkeychain_darwin.h | 2 + osxkeychain/osxkeychain_darwin_test.go | 6 +- 7 files changed, 170 insertions(+), 3 deletions(-) diff --git a/credentials/credentials.go b/credentials/credentials.go index b14f495..23fa569 100644 --- a/credentials/credentials.go +++ b/credentials/credentials.go @@ -17,6 +17,11 @@ type Credentials struct { Secret string } +type KeyData struct{ + Path string + Username string +} + // Serve initializes the credentials helper and parses the action argument. // This function is designed to be called from a command line interface. // It uses os.Args[1] as the key for the action. @@ -127,3 +132,26 @@ func Erase(helper Helper, reader io.Reader) error { return helper.Delete(serverURL) } + + //List returns all the serverURLs of keys in + //the OS store as a list of strings +func List(helper Helper, writer io.Writer) error { + x, y, err := helper.List() + if err != nil { + return err + } + keyDataList := []KeyData{} + for index, _ := range(x) { + keyDataObj := KeyData{ + Path:x[index], + Username:y[index], + } + keyDataList = append([]KeyData{keyDataObj}, keyDataList...) + } + buffer := new(bytes.Buffer) + if err := json.NewEncoder(buffer).Encode(keyDataList); err != nil { + return err + } + fmt.Fprint(writer, buffer.String()) + return nil +} \ No newline at end of file diff --git a/credentials/credentials_test.go b/credentials/credentials_test.go index fae219a..0909cb7 100644 --- a/credentials/credentials_test.go +++ b/credentials/credentials_test.go @@ -36,6 +36,11 @@ func (m *memoryStore) Get(serverURL string) (string, string, error) { return c.Username, c.Secret, nil } +func (m *memoryStore) List() ([]string, []string, error) { + //Simply a placeholder to let memoryStore be a valid implementation of Helper interface + return nil, nil, nil +} + func TestStore(t *testing.T) { serverURL := "https://index.docker.io/v1/" creds := &Credentials{ @@ -138,3 +143,16 @@ func TestErase(t *testing.T) { t.Fatal("expected error getting missing creds, got empty") } } + +func TestList(t *testing.T) { + //This tests that there is proper input an output into the byte stream + //Individual stores are very OS specific and have been tested in osxkeychain and secretservice respectively + out := new(bytes.Buffer) + h := newMemoryStore() + if err := List(h, out); err != nil { + t.Fatal(err) + } + if out.Len() == 0 { + t.Fatalf("expected output in the writer, got %d", 0) + } +} diff --git a/credentials/helper.go b/credentials/helper.go index 8a69671..ba1ba34 100644 --- a/credentials/helper.go +++ b/credentials/helper.go @@ -9,4 +9,7 @@ type Helper interface { // Get retrieves credentials from the store. // It returns username and secret as strings. Get(serverURL string) (string, string, error) + // List returns all the serverURLs of keys in + // the OS store as a list of strings + List() ([]string, []string, error) } diff --git a/osxkeychain/osxkeychain_darwin.c b/osxkeychain/osxkeychain_darwin.c index d1289e4..2b4d8d8 100644 --- a/osxkeychain/osxkeychain_darwin.c +++ b/osxkeychain/osxkeychain_darwin.c @@ -1,4 +1,7 @@ #include "osxkeychain_darwin.h" +#include +#include +#include char *get_error(OSStatus status) { char *buf = malloc(128); @@ -96,3 +99,77 @@ char *keychain_delete(struct Server *server) { } return NULL; } + +char * CFStringToCharArr(CFStringRef aString) { + if (aString == NULL) { + return NULL; + } + CFIndex length = CFStringGetLength(aString); + CFIndex maxSize = + CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; + char *buffer = (char *)malloc(maxSize); + if (CFStringGetCString(aString, buffer, maxSize, + kCFStringEncodingUTF8)) { + return buffer; + } + return NULL; +} + +char *keychain_list(char *** paths, char *** accts, unsigned int *list_l) { + CFMutableDictionaryRef query = CFDictionaryCreateMutable (NULL, 1, NULL, NULL); + CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword); + CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue); + CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll); + //Use this query dictionary + CFTypeRef result= NULL; + OSStatus status = SecItemCopyMatching( + query, + &result); + //Ran a search and store the results in result + if (status) { + return get_error(status); + } + int numKeys = CFArrayGetCount(result); + *paths = (char **) malloc((int)sizeof(char *)*numKeys); + *accts = (char **) malloc((int)sizeof(char *)*numKeys); + //result is of type CFArray + for(int i=0; i"); + } + char * path = (char *) malloc(CFStringGetLength(pathTmp)+1); + path = CFStringToCharArr(pathTmp); + path[strlen(path)] = '\0'; + char * acct = (char *) malloc(CFStringGetLength(acctTmp)+1); //<- problem line in 38th iteration + acct = CFStringToCharArr(acctTmp); + acct[strlen(acct)] = '\0'; + //We now have all we need, username and servername. Now export this to .go + (*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)+1)); + memcpy((*paths)[i], path, sizeof(char)*(strlen(path)+1)); + (*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)+1)); + memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)+1)); + } + else { + char * path = "0"; + char * acct = "0"; + (*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path))); + memcpy((*paths)[i], path, sizeof(char)*(strlen(path))); + (*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct))); + memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct))); + } + } + *list_l = numKeys; + return NULL; +} + +void freeListData(char *** data, unsigned int length) { + for(int i=0; i> + C.freeListData(&pathsC, listLenC) + return paths, accts, nil +} + func splitServer(serverURL string) (*C.struct_Server, error) { u, err := url.Parse(serverURL) if err != nil { @@ -130,3 +164,4 @@ func freeServer(s *C.struct_Server) { C.free(unsafe.Pointer(s.host)) C.free(unsafe.Pointer(s.path)) } + diff --git a/osxkeychain/osxkeychain_darwin.h b/osxkeychain/osxkeychain_darwin.h index cb463c4..6b01e97 100644 --- a/osxkeychain/osxkeychain_darwin.h +++ b/osxkeychain/osxkeychain_darwin.h @@ -10,3 +10,5 @@ struct Server { char *keychain_add(struct Server *server, char *username, char *secret); char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret); char *keychain_delete(struct Server *server); +char *keychain_list(char *** data, char *** accts, unsigned int *list_l); +void freeListData(char *** data, unsigned int length); \ No newline at end of file diff --git a/osxkeychain/osxkeychain_darwin_test.go b/osxkeychain/osxkeychain_darwin_test.go index 175da26..94c47aa 100644 --- a/osxkeychain/osxkeychain_darwin_test.go +++ b/osxkeychain/osxkeychain_darwin_test.go @@ -2,7 +2,6 @@ package osxkeychain import ( "testing" - "github.com/docker/docker-credential-helpers/credentials" ) @@ -34,6 +33,11 @@ func TestOSXKeychainHelper(t *testing.T) { if err := helper.Delete(creds.ServerURL); err != nil { t.Fatal(err) } + + _, _, err = helper.List(); + if err != nil { + t.Fatal(err) + } } func TestMissingCredentials(t *testing.T) {