Skip to content

Commit 14381bf

Browse files
authored
Merge pull request #49 from n4ss/label-creds
Label credentials on each platform' creds store and fix secretservice behavior
2 parents 1057cf7 + 7133af5 commit 14381bf

File tree

8 files changed

+109
-36
lines changed

8 files changed

+109
-36
lines changed

credentials/credentials.go

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

20+
// Docker credentials should be labeled as such in credentials stores that allow labelling.
21+
// That label allows to filter out non-Docker credentials too at lookup/search in macOS keychain,
22+
// Windows credentials manager and Linux libsecret. Default value is "Docker Credentials"
23+
var CredsLabel = "Docker Credentials"
24+
25+
func SetCredsLabel(label string) {
26+
CredsLabel = label
27+
}
28+
2029
// Serve initializes the credentials helper and parses the action argument.
2130
// This function is designed to be called from a command line interface.
2231
// It uses os.Args[1] as the key for the action.

osxkeychain/osxkeychain_darwin.c

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ char *get_error(OSStatus status) {
1414
return buf;
1515
}
1616

17-
char *keychain_add(struct Server *server, char *username, char *secret) {
17+
char *keychain_add(struct Server *server, char *label, char *username, char *secret) {
18+
SecKeychainItemRef item;
19+
1820
OSStatus status = SecKeychainAddInternetPassword(
1921
NULL,
2022
strlen(server->host), server->host,
@@ -25,11 +27,27 @@ char *keychain_add(struct Server *server, char *username, char *secret) {
2527
server->proto,
2628
kSecAuthenticationTypeDefault,
2729
strlen(secret), secret,
28-
NULL
30+
&item
2931
);
32+
33+
if (status) {
34+
return get_error(status);
35+
}
36+
37+
SecKeychainAttribute attribute;
38+
SecKeychainAttributeList attrs;
39+
attribute.tag = kSecLabelItemAttr;
40+
attribute.data = label;
41+
attribute.length = strlen(label);
42+
attrs.count = 1;
43+
attrs.attr = &attribute;
44+
45+
status = SecKeychainItemModifyContent(item, &attrs, 0, NULL);
46+
3047
if (status) {
3148
return get_error(status);
3249
}
50+
3351
return NULL;
3452
}
3553

@@ -116,16 +134,21 @@ char * CFStringToCharArr(CFStringRef aString) {
116134
return NULL;
117135
}
118136

119-
char *keychain_list(char *** paths, char *** accts, unsigned int *list_l) {
137+
char *keychain_list(char *credsLabel, char *** paths, char *** accts, unsigned int *list_l) {
138+
CFStringRef credsLabelCF = CFStringCreateWithCString(NULL, credsLabel, kCFStringEncodingUTF8);
120139
CFMutableDictionaryRef query = CFDictionaryCreateMutable (NULL, 1, NULL, NULL);
121140
CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
122141
CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue);
123142
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
143+
CFDictionaryAddValue(query, kSecAttrLabel, credsLabelCF);
124144
//Use this query dictionary
125145
CFTypeRef result= NULL;
126146
OSStatus status = SecItemCopyMatching(
127147
query,
128148
&result);
149+
150+
CFRelease(credsLabelCF);
151+
129152
//Ran a search and store the results in result
130153
if (status) {
131154
return get_error(status);

osxkeychain/osxkeychain_darwin.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ func (h Osxkeychain) Add(creds *credentials.Credentials) error {
3535
}
3636
defer freeServer(s)
3737

38+
label := C.CString(credentials.CredsLabel)
39+
defer C.free(unsafe.Pointer(label))
3840
username := C.CString(creds.Username)
3941
defer C.free(unsafe.Pointer(username))
4042
secret := C.CString(creds.Secret)
4143
defer C.free(unsafe.Pointer(secret))
4244

43-
errMsg := C.keychain_add(s, username, secret)
45+
errMsg := C.keychain_add(s, label, username, secret)
4446
if errMsg != nil {
4547
defer C.free(unsafe.Pointer(errMsg))
4648
return errors.New(C.GoString(errMsg))
@@ -99,12 +101,15 @@ func (h Osxkeychain) Get(serverURL string) (string, string, error) {
99101

100102
// List returns the stored URLs and corresponding usernames.
101103
func (h Osxkeychain) List() (map[string]string, error) {
104+
credsLabelC := C.CString(credentials.CredsLabel)
105+
defer C.free(unsafe.Pointer(credsLabelC))
106+
102107
var pathsC **C.char
103108
defer C.free(unsafe.Pointer(pathsC))
104109
var acctsC **C.char
105110
defer C.free(unsafe.Pointer(acctsC))
106111
var listLenC C.uint
107-
errMsg := C.keychain_list(&pathsC, &acctsC, &listLenC)
112+
errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC)
108113
if errMsg != nil {
109114
defer C.free(unsafe.Pointer(errMsg))
110115
goMsg := C.GoString(errMsg)

osxkeychain/osxkeychain_darwin.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ struct Server {
77
unsigned int port;
88
};
99

10-
char *keychain_add(struct Server *server, char *username, char *secret);
10+
char *keychain_add(struct Server *server, char *label, 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);
13+
char *keychain_list(char *credsLabel, char *** data, char *** accts, unsigned int *list_l);
1414
void freeListData(char *** data, unsigned int length);

secretservice/secretservice_linux.c

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const SecretSchema *docker_get_schema(void)
77
static const SecretSchema docker_schema = {
88
"io.docker.Credentials", SECRET_SCHEMA_NONE,
99
{
10+
{ "label", SECRET_SCHEMA_ATTRIBUTE_STRING },
1011
{ "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
1112
{ "username", SECRET_SCHEMA_ATTRIBUTE_STRING },
1213
{ "docker_cli", SECRET_SCHEMA_ATTRIBUTE_STRING },
@@ -16,11 +17,12 @@ const SecretSchema *docker_get_schema(void)
1617
return &docker_schema;
1718
}
1819

19-
GError *add(char *server, char *username, char *secret) {
20+
GError *add(char *label, char *server, char *username, char *secret) {
2021
GError *err = NULL;
2122

2223
secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT,
2324
server, secret, NULL, &err,
25+
"label", label,
2426
"server", server,
2527
"username", username,
2628
"docker_cli", "1",
@@ -40,15 +42,15 @@ GError *delete(char *server) {
4042
return NULL;
4143
}
4244

43-
char *get_username(SecretItem *item) {
45+
char *get_attribute(const char *attribute, SecretItem *item) {
4446
GHashTable *attributes;
4547
GHashTableIter iter;
4648
gchar *value, *key;
4749

4850
attributes = secret_item_get_attributes(item);
4951
g_hash_table_iter_init(&iter, attributes);
5052
while (g_hash_table_iter_next(&iter, (void **)&key, (void **)&value)) {
51-
if (strncmp(key, "username", strlen(key)) == 0)
53+
if (strncmp(key, attribute, strlen(key)) == 0)
5254
return (char *)value;
5355
}
5456
g_hash_table_unref(attributes);
@@ -71,7 +73,7 @@ GError *get(char *server, char **username, char **secret) {
7173

7274
service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err);
7375
if (err == NULL) {
74-
items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err);
76+
items = secret_service_search_sync(service, DOCKER_SCHEMA, attributes, flags, NULL, &err);
7577
if (err == NULL) {
7678
for (l = items; l != NULL; l = g_list_next(l)) {
7779
value = secret_item_get_schema_name(l->data);
@@ -85,7 +87,7 @@ GError *get(char *server, char **username, char **secret) {
8587
*secret = strdup(secret_value_get(secretValue, &length));
8688
secret_value_unref(secretValue);
8789
}
88-
*username = get_username(l->data);
90+
*username = get_attribute("username", l->data);
8991
}
9092
g_list_free_full(items, g_object_unref);
9193
}
@@ -98,44 +100,56 @@ GError *get(char *server, char **username, char **secret) {
98100
return NULL;
99101
}
100102

101-
GError *list(char *** paths, char *** accts, unsigned int *list_l) {
103+
GError *list(char *ref_label, char *** paths, char *** accts, unsigned int *list_l) {
102104
GList *items;
103105
GError *err = NULL;
104106
SecretService *service;
105107
SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK;
106-
GHashTable *attributes;
107-
g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
108-
attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
108+
GHashTable *attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
109+
110+
// List credentials with the right label only
111+
g_hash_table_insert(attributes, g_strdup("label"), g_strdup(ref_label));
112+
109113
service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err);
114+
if (err != NULL) {
115+
return err;
116+
}
117+
110118
items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err);
111119
int numKeys = g_list_length(items);
112120
if (err != NULL) {
113121
return err;
114122
}
115-
*paths = (char **) malloc((int)sizeof(char *)*numKeys);
116-
*accts = (char **) malloc((int)sizeof(char *)*numKeys);
123+
124+
char **tmp_paths = (char **) calloc(1,(int)sizeof(char *)*numKeys);
125+
char **tmp_accts = (char **) calloc(1,(int)sizeof(char *)*numKeys);
126+
117127
// items now contains our keys from the gnome keyring
118128
// we will now put it in our two lists to return it to go
119129
GList *current;
120130
int listNumber = 0;
121131
for(current = items; current!=NULL; current = current->next) {
122132
char *pathTmp = secret_item_get_label(current->data);
123133
// you cannot have a key without a label in the gnome keyring
124-
char *acctTmp = get_username(current->data);
134+
char *acctTmp = get_attribute("username",current->data);
125135
if (acctTmp==NULL) {
126136
acctTmp = "account not defined";
127137
}
128-
char *path = (char *) malloc(strlen(pathTmp));
129-
char *acct = (char *) malloc(strlen(acctTmp));
130-
path = pathTmp;
131-
acct = acctTmp;
132-
(*paths)[listNumber] = (char *) malloc(sizeof(char)*(strlen(path)));
133-
memcpy((*paths)[listNumber], path, sizeof(char)*(strlen(path)));
134-
(*accts)[listNumber] = (char *) malloc(sizeof(char)*(strlen(acct)));
135-
memcpy((*accts)[listNumber], acct, sizeof(char)*(strlen(acct)));
138+
139+
tmp_paths[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(pathTmp)+1));
140+
tmp_accts[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(acctTmp)+1));
141+
142+
memcpy(tmp_paths[listNumber], pathTmp, sizeof(char)*(strlen(pathTmp)+1));
143+
memcpy(tmp_accts[listNumber], acctTmp, sizeof(char)*(strlen(acctTmp)+1));
144+
136145
listNumber = listNumber + 1;
137146
}
138-
*list_l = numKeys;
147+
148+
*paths = (char **) realloc(tmp_paths, (int)sizeof(char *)*listNumber);
149+
*accts = (char **) realloc(tmp_accts, (int)sizeof(char *)*listNumber);
150+
151+
*list_l = listNumber;
152+
139153
return NULL;
140154
}
141155

secretservice/secretservice_linux.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ func (h Secretservice) Add(creds *credentials.Credentials) error {
2222
if creds == nil {
2323
return errors.New("missing credentials")
2424
}
25+
credsLabel := C.CString(credentials.CredsLabel)
26+
defer C.free(unsafe.Pointer(credsLabel))
2527
server := C.CString(creds.ServerURL)
2628
defer C.free(unsafe.Pointer(server))
2729
username := C.CString(creds.Username)
2830
defer C.free(unsafe.Pointer(username))
2931
secret := C.CString(creds.Secret)
3032
defer C.free(unsafe.Pointer(secret))
3133

32-
if err := C.add(server, username, secret); err != nil {
34+
if err := C.add(credsLabel, server, username, secret); err != nil {
3335
defer C.g_error_free(err)
3436
errMsg := (*C.char)(unsafe.Pointer(err.message))
3537
return errors.New(C.GoString(errMsg))
@@ -79,25 +81,32 @@ func (h Secretservice) Get(serverURL string) (string, string, error) {
7981
return user, pass, nil
8082
}
8183

82-
// List returns the stored URLs and corresponding usernames.
84+
// List returns the stored URLs and corresponding usernames for a given credentials label
8385
func (h Secretservice) List() (map[string]string, error) {
86+
credsLabelC := C.CString(credentials.CredsLabel)
87+
defer C.free(unsafe.Pointer(credsLabelC))
88+
8489
var pathsC **C.char
8590
defer C.free(unsafe.Pointer(pathsC))
8691
var acctsC **C.char
8792
defer C.free(unsafe.Pointer(acctsC))
8893
var listLenC C.uint
89-
err := C.list(&pathsC, &acctsC, &listLenC)
94+
err := C.list(credsLabelC, &pathsC, &acctsC, &listLenC)
9095
if err != nil {
9196
defer C.free(unsafe.Pointer(err))
9297
return nil, errors.New("Error from list function in secretservice_linux.c likely due to error in secretservice library")
9398
}
9499
defer C.freeListData(&pathsC, listLenC)
95100
defer C.freeListData(&acctsC, listLenC)
96101

102+
resp := make(map[string]string)
103+
97104
listLen := int(listLenC)
105+
if listLen == 0 {
106+
return resp, nil
107+
}
98108
pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
99109
acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
100-
resp := make(map[string]string)
101110
for i := 0; i < listLen; i++ {
102111
resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
103112
}

secretservice/secretservice_linux.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ const SecretSchema *docker_get_schema(void) G_GNUC_CONST;
66

77
#define DOCKER_SCHEMA docker_get_schema()
88

9-
GError *add(char *server, char *username, char *secret);
9+
GError *add(char *label, char *server, char *username, char *secret);
1010
GError *delete(char *server);
1111
GError *get(char *server, char **username, char **secret);
12-
GError *list(char *** paths, char *** accts, unsigned int *list_l);
12+
GError *list(char *label, char *** paths, char *** accts, unsigned int *list_l);
1313
void freeListData(char *** data, unsigned int length);

wincred/wincred_windows.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package wincred
22

33
import (
4+
"bytes"
45
winc "github.com/danieljoos/wincred"
56
"github.com/docker/docker-credential-helpers/credentials"
7+
"strings"
68
)
79

810
// Wincred handles secrets using the Windows credential service.
@@ -14,6 +16,8 @@ func (h Wincred) Add(creds *credentials.Credentials) error {
1416
g.UserName = creds.Username
1517
g.CredentialBlob = []byte(creds.Secret)
1618
g.Persist = winc.PersistLocalMachine
19+
g.Attributes = []winc.CredentialAttribute{{"label", []byte(credentials.CredsLabel)}}
20+
1721
return g.Write()
1822
}
1923

@@ -38,7 +42,7 @@ func (h Wincred) Get(serverURL string) (string, string, error) {
3842
return g.UserName, string(g.CredentialBlob), nil
3943
}
4044

41-
// List returns the stored URLs and corresponding usernames.
45+
// List returns the stored URLs and corresponding usernames for a given credentials label.
4246
func (h Wincred) List() (map[string]string, error) {
4347
creds, err := winc.List()
4448
if err != nil {
@@ -47,7 +51,16 @@ func (h Wincred) List() (map[string]string, error) {
4751

4852
resp := make(map[string]string)
4953
for i := range creds {
50-
resp[creds[i].TargetName] = creds[i].UserName
54+
attrs = creds[i].Attributes
55+
for _, attr := range attrs {
56+
if !strings.Compare(attr.Keyword, "label") &&
57+
!bytes.Compare(attr.Value, []byte(credentials.CredsLabel)) {
58+
59+
resp[creds[i].TargetName] = creds[i].UserName
60+
}
61+
}
62+
5163
}
64+
5265
return resp, nil
5366
}

0 commit comments

Comments
 (0)