diff --git a/credentials/credentials.go b/credentials/credentials.go index 3c4eec7..cfe0cb1 100644 --- a/credentials/credentials.go +++ b/credentials/credentials.go @@ -17,6 +17,15 @@ type Credentials struct { Secret string } +// Docker credentials should be labeled as such in credentials stores that allow labelling. +// That label allows to filter out non-Docker credentials too at lookup/search in macOS keychain, +// Windows credentials manager and Linux libsecret. Default value is "Docker Credentials" +var CredsLabel = "Docker Credentials" + +func SetCredsLabel(label string) { + CredsLabel = label +} + // 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. diff --git a/osxkeychain/osxkeychain_darwin.c b/osxkeychain/osxkeychain_darwin.c index 371222e..f84d61e 100644 --- a/osxkeychain/osxkeychain_darwin.c +++ b/osxkeychain/osxkeychain_darwin.c @@ -14,7 +14,9 @@ char *get_error(OSStatus status) { return buf; } -char *keychain_add(struct Server *server, char *username, char *secret) { +char *keychain_add(struct Server *server, char *label, char *username, char *secret) { + SecKeychainItemRef item; + OSStatus status = SecKeychainAddInternetPassword( NULL, strlen(server->host), server->host, @@ -25,11 +27,27 @@ char *keychain_add(struct Server *server, char *username, char *secret) { server->proto, kSecAuthenticationTypeDefault, strlen(secret), secret, - NULL + &item ); + if (status) { return get_error(status); } + + SecKeychainAttribute attribute; + SecKeychainAttributeList attrs; + attribute.tag = kSecLabelItemAttr; + attribute.data = label; + attribute.length = strlen(label); + attrs.count = 1; + attrs.attr = &attribute; + + status = SecKeychainItemModifyContent(item, &attrs, 0, NULL); + + if (status) { + return get_error(status); + } + return NULL; } @@ -116,16 +134,21 @@ char * CFStringToCharArr(CFStringRef aString) { return NULL; } -char *keychain_list(char *** paths, char *** accts, unsigned int *list_l) { +char *keychain_list(char *credsLabel, char *** paths, char *** accts, unsigned int *list_l) { + CFStringRef credsLabelCF = CFStringCreateWithCString(NULL, credsLabel, kCFStringEncodingUTF8); CFMutableDictionaryRef query = CFDictionaryCreateMutable (NULL, 1, NULL, NULL); CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword); CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue); CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll); + CFDictionaryAddValue(query, kSecAttrLabel, credsLabelCF); //Use this query dictionary CFTypeRef result= NULL; OSStatus status = SecItemCopyMatching( query, &result); + + CFRelease(credsLabelCF); + //Ran a search and store the results in result if (status) { return get_error(status); diff --git a/osxkeychain/osxkeychain_darwin.go b/osxkeychain/osxkeychain_darwin.go index 21761aa..a3f5da8 100644 --- a/osxkeychain/osxkeychain_darwin.go +++ b/osxkeychain/osxkeychain_darwin.go @@ -35,12 +35,14 @@ func (h Osxkeychain) Add(creds *credentials.Credentials) error { } 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, username, secret) + errMsg := C.keychain_add(s, label, username, secret) if errMsg != nil { defer C.free(unsafe.Pointer(errMsg)) return errors.New(C.GoString(errMsg)) @@ -99,12 +101,15 @@ func (h Osxkeychain) Get(serverURL string) (string, string, error) { // 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(&pathsC, &acctsC, &listLenC) + errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC) if errMsg != nil { defer C.free(unsafe.Pointer(errMsg)) goMsg := C.GoString(errMsg) diff --git a/osxkeychain/osxkeychain_darwin.h b/osxkeychain/osxkeychain_darwin.h index 6b01e97..c54e7d7 100644 --- a/osxkeychain/osxkeychain_darwin.h +++ b/osxkeychain/osxkeychain_darwin.h @@ -7,8 +7,8 @@ struct Server { unsigned int port; }; -char *keychain_add(struct Server *server, char *username, char *secret); +char *keychain_add(struct Server *server, char *label, 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); +char *keychain_list(char *credsLabel, char *** data, char *** accts, unsigned int *list_l); void freeListData(char *** data, unsigned int length); \ No newline at end of file diff --git a/secretservice/secretservice_linux.c b/secretservice/secretservice_linux.c index ab23a5e..35dea92 100644 --- a/secretservice/secretservice_linux.c +++ b/secretservice/secretservice_linux.c @@ -7,6 +7,7 @@ const SecretSchema *docker_get_schema(void) static const SecretSchema docker_schema = { "io.docker.Credentials", SECRET_SCHEMA_NONE, { + { "label", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "server", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "username", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "docker_cli", SECRET_SCHEMA_ATTRIBUTE_STRING }, @@ -16,11 +17,12 @@ const SecretSchema *docker_get_schema(void) return &docker_schema; } -GError *add(char *server, char *username, char *secret) { +GError *add(char *label, char *server, char *username, char *secret) { GError *err = NULL; secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT, server, secret, NULL, &err, + "label", label, "server", server, "username", username, "docker_cli", "1", @@ -40,7 +42,7 @@ GError *delete(char *server) { return NULL; } -char *get_username(SecretItem *item) { +char *get_attribute(const char *attribute, SecretItem *item) { GHashTable *attributes; GHashTableIter iter; gchar *value, *key; @@ -48,7 +50,7 @@ char *get_username(SecretItem *item) { attributes = secret_item_get_attributes(item); g_hash_table_iter_init(&iter, attributes); while (g_hash_table_iter_next(&iter, (void **)&key, (void **)&value)) { - if (strncmp(key, "username", strlen(key)) == 0) + if (strncmp(key, attribute, strlen(key)) == 0) return (char *)value; } g_hash_table_unref(attributes); @@ -71,7 +73,7 @@ GError *get(char *server, char **username, char **secret) { service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err); if (err == NULL) { - items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err); + items = secret_service_search_sync(service, DOCKER_SCHEMA, attributes, flags, NULL, &err); if (err == NULL) { for (l = items; l != NULL; l = g_list_next(l)) { value = secret_item_get_schema_name(l->data); @@ -85,7 +87,7 @@ GError *get(char *server, char **username, char **secret) { *secret = strdup(secret_value_get(secretValue, &length)); secret_value_unref(secretValue); } - *username = get_username(l->data); + *username = get_attribute("username", l->data); } g_list_free_full(items, g_object_unref); } @@ -98,22 +100,30 @@ GError *get(char *server, char **username, char **secret) { return NULL; } -GError *list(char *** paths, char *** accts, unsigned int *list_l) { +GError *list(char *ref_label, 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); + GHashTable *attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + + // List credentials with the right label only + g_hash_table_insert(attributes, g_strdup("label"), g_strdup(ref_label)); + service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err); + if (err != NULL) { + return 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); + + char **tmp_paths = (char **) calloc(1,(int)sizeof(char *)*numKeys); + char **tmp_accts = (char **) calloc(1,(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; @@ -121,21 +131,25 @@ GError *list(char *** paths, char *** accts, unsigned int *list_l) { 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); + char *acctTmp = get_attribute("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))); + + tmp_paths[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(pathTmp)+1)); + tmp_accts[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(acctTmp)+1)); + + memcpy(tmp_paths[listNumber], pathTmp, sizeof(char)*(strlen(pathTmp)+1)); + memcpy(tmp_accts[listNumber], acctTmp, sizeof(char)*(strlen(acctTmp)+1)); + listNumber = listNumber + 1; } - *list_l = numKeys; + + *paths = (char **) realloc(tmp_paths, (int)sizeof(char *)*listNumber); + *accts = (char **) realloc(tmp_accts, (int)sizeof(char *)*listNumber); + + *list_l = listNumber; + return NULL; } diff --git a/secretservice/secretservice_linux.go b/secretservice/secretservice_linux.go index f3264ce..ec1c5d3 100644 --- a/secretservice/secretservice_linux.go +++ b/secretservice/secretservice_linux.go @@ -22,6 +22,8 @@ func (h Secretservice) Add(creds *credentials.Credentials) error { if creds == nil { return errors.New("missing credentials") } + credsLabel := C.CString(credentials.CredsLabel) + defer C.free(unsafe.Pointer(credsLabel)) server := C.CString(creds.ServerURL) defer C.free(unsafe.Pointer(server)) username := C.CString(creds.Username) @@ -29,7 +31,7 @@ func (h Secretservice) Add(creds *credentials.Credentials) error { secret := C.CString(creds.Secret) defer C.free(unsafe.Pointer(secret)) - if err := C.add(server, username, secret); err != nil { + if err := C.add(credsLabel, server, username, secret); err != nil { defer C.g_error_free(err) errMsg := (*C.char)(unsafe.Pointer(err.message)) return errors.New(C.GoString(errMsg)) @@ -79,14 +81,17 @@ func (h Secretservice) Get(serverURL string) (string, string, error) { return user, pass, nil } -// List returns the stored URLs and corresponding usernames. +// List returns the stored URLs and corresponding usernames for a given credentials label func (h Secretservice) 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 - err := C.list(&pathsC, &acctsC, &listLenC) + err := C.list(credsLabelC, &pathsC, &acctsC, &listLenC) if err != nil { defer C.free(unsafe.Pointer(err)) return nil, errors.New("Error from list function in secretservice_linux.c likely due to error in secretservice library") @@ -94,10 +99,14 @@ func (h Secretservice) List() (map[string]string, error) { defer C.freeListData(&pathsC, listLenC) defer C.freeListData(&acctsC, listLenC) + resp := make(map[string]string) + listLen := int(listLenC) + if listLen == 0 { + return resp, nil + } pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen] acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen] - resp := make(map[string]string) for i := 0; i < listLen; i++ { resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i]) } diff --git a/secretservice/secretservice_linux.h b/secretservice/secretservice_linux.h index 319cdc0..a28179d 100644 --- a/secretservice/secretservice_linux.h +++ b/secretservice/secretservice_linux.h @@ -6,8 +6,8 @@ const SecretSchema *docker_get_schema(void) G_GNUC_CONST; #define DOCKER_SCHEMA docker_get_schema() -GError *add(char *server, char *username, char *secret); +GError *add(char *label, char *server, char *username, char *secret); GError *delete(char *server); GError *get(char *server, char **username, char **secret); -GError *list(char *** paths, char *** accts, unsigned int *list_l); +GError *list(char *label, char *** paths, char *** accts, unsigned int *list_l); void freeListData(char *** data, unsigned int length); diff --git a/wincred/wincred_windows.go b/wincred/wincred_windows.go index 294c939..18e442e 100644 --- a/wincred/wincred_windows.go +++ b/wincred/wincred_windows.go @@ -1,8 +1,10 @@ package wincred import ( + "bytes" winc "github.com/danieljoos/wincred" "github.com/docker/docker-credential-helpers/credentials" + "strings" ) // Wincred handles secrets using the Windows credential service. @@ -14,6 +16,8 @@ func (h Wincred) Add(creds *credentials.Credentials) error { g.UserName = creds.Username g.CredentialBlob = []byte(creds.Secret) g.Persist = winc.PersistLocalMachine + g.Attributes = []winc.CredentialAttribute{{"label", []byte(credentials.CredsLabel)}} + return g.Write() } @@ -38,7 +42,7 @@ func (h Wincred) Get(serverURL string) (string, string, error) { return g.UserName, string(g.CredentialBlob), nil } -// List returns the stored URLs and corresponding usernames. +// List returns the stored URLs and corresponding usernames for a given credentials label. func (h Wincred) List() (map[string]string, error) { creds, err := winc.List() if err != nil { @@ -47,7 +51,16 @@ func (h Wincred) List() (map[string]string, error) { resp := make(map[string]string) for i := range creds { - resp[creds[i].TargetName] = creds[i].UserName + attrs = creds[i].Attributes + for _, attr := range attrs { + if !strings.Compare(attr.Keyword, "label") && + !bytes.Compare(attr.Value, []byte(credentials.CredsLabel)) { + + resp[creds[i].TargetName] = creds[i].UserName + } + } + } + return resp, nil }