diff --git a/client/client.go b/client/client.go index ddd30bb..469754c 100644 --- a/client/client.go +++ b/client/client.go @@ -55,11 +55,10 @@ func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error return resp, nil } -// Erase executes a program to remove the server credentails from the native store. +// Erase executes a program to remove the server credentials from the native store. func Erase(program ProgramFunc, serverURL string) error { cmd := program("erase") cmd.Input(strings.NewReader(serverURL)) - out, err := cmd.Output() if err != nil { t := strings.TrimSpace(string(out)) @@ -68,3 +67,15 @@ func Erase(program ProgramFunc, serverURL string) error { return nil } + +// List executes a program to remove the server credentials from the native store. +func List(program ProgramFunc) error { + cmd := program("list") + cmd.Input(strings.NewReader("garbage")) + out, err := cmd.Output() + if err != nil { + t := strings.TrimSpace(string(out)) + return fmt.Errorf("error listing credentials - err: %v, out: `%s`", err, t) + } + return nil +} diff --git a/client/client_test.go b/client/client_test.go index 1f2ee53..1f8b4bd 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -70,6 +70,9 @@ func (m *mockProgram) Output() ([]byte, error) { default: return []byte("error storing credentials"), errProgramExited } + case "list": + return []byte(`{"Path":"e237574ae22fd53ddb9490dc1f72139946fd5372d42ba54d1eeb3ae5068fd22b","Username":"http://example.com/collections\u003cnotary_key\u003eSnapshot"}`), nil + } return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errProgramExited @@ -190,3 +193,9 @@ func TestErase(t *testing.T) { t.Fatalf("Expected error for server %s, got nil", invalidServerAddress) } } + +func TestList(t *testing.T) { + if err := List(mockProgramFn); err != nil { + t.Fatal(err) + } +} diff --git a/credentials/credentials.go b/credentials/credentials.go index b14f495..6d7259b 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. @@ -25,7 +30,7 @@ type Credentials struct { func Serve(helper Helper) { var err error if len(os.Args) != 2 { - err = fmt.Errorf("Usage: %s ", os.Args[0]) + err = fmt.Errorf("Usage: %s ", os.Args[0]) } if err == nil { @@ -47,6 +52,8 @@ func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error return Get(helper, in, out) case "erase": return Erase(helper, in) + case "list": + return List(helper, out) } return fmt.Errorf("Unknown credential action `%s`", key) } @@ -127,3 +134,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 { + paths, accts, err := helper.List() + if err != nil { + return err + } + keyDataList := []KeyData{} + for index := 0; index < len(paths); index++ { + keyDataObj := KeyData{ + Path: paths[index], + Username: accts[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 +} diff --git a/credentials/credentials_test.go b/credentials/credentials_test.go index fae219a..6476012 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,17 @@ 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) + } + //testing that there is an output + 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..785fcd7 100644 --- a/credentials/helper.go +++ b/credentials/helper.go @@ -9,4 +9,8 @@ type Helper interface { // Get retrieves credentials from the store. // It returns username and secret as strings. Get(serverURL string) (string, string, error) + // List returns the serverURLs of keys and their + // associated usernames from 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..d8105fc 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 +#include #include "secretservice_linux.h" const SecretSchema *docker_get_schema(void) @@ -96,3 +97,52 @@ GError *get(char *server, char **username, char **secret) { } return NULL; } + +GError *list(char *** paths, char *** accts, unsigned int *list_l) { + GList *items; + GError *err = NULL; + SecretService *service; + SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK; + GHashTable *attributes; + g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err); + items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err); + int numKeys = g_list_length(items); + if (err != NULL) { + return err; + } + *paths = (char **) malloc((int)sizeof(char *)*numKeys); + *accts = (char **) malloc((int)sizeof(char *)*numKeys); + // items now contains our keys from the gnome keyring + // we will now put it in our two lists to return it to go + GList *current; + int listNumber = 0; + for(current = items; current!=NULL; current = current->next) { + char *pathTmp = secret_item_get_label(current->data); + // you cannot have a key without a label in the gnome keyring + char *acctTmp = get_username(current->data); + if (acctTmp==NULL) { + acctTmp = "account not defined"; + } + char *path = (char *) malloc(strlen(pathTmp)); + char *acct = (char *) malloc(strlen(acctTmp)); + path = pathTmp; + acct = acctTmp; + (*paths)[listNumber] = (char *) malloc(sizeof(char)*(strlen(path))); + memcpy((*paths)[listNumber], path, sizeof(char)*(strlen(path))); + (*accts)[listNumber] = (char *) malloc(sizeof(char)*(strlen(acct))); + memcpy((*accts)[listNumber], acct, sizeof(char)*(strlen(acct))); + listNumber = listNumber + 1; + } + *list_l = numKeys; + return NULL; +} + +void freeListData(char *** data, unsigned int length) { + int i; + for(i=0; i