1
0
mirror of https://github.com/docker/docker-credential-helpers.git synced 2026-06-13 16:01:28 +05:30

🌅 complete OS X keychain implementation.

Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
David Calavera
2016-02-07 17:16:54 -08:00
commit b7ffbdb74f
13 changed files with 478 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
package main
import (
"github.com/calavera/docker-credential-helpers/osxkeychain"
"github.com/calavera/docker-credential-helpers/plugin"
)
func main() {
plugin.Serve(osxkeychain.New())
}
+98
View File
@@ -0,0 +1,98 @@
#include "osxkeychain_darwin.h"
char *get_error(OSStatus status) {
char *buf = malloc(128);
CFStringRef str = SecCopyErrorMessageString(status, NULL);
int success = CFStringGetCString(str, buf, 128, kCFStringEncodingUTF8);
if (success) {
strncpy(buf, "Unknown error", 128);
}
return buf;
}
char *keychain_add(struct Server *server, char *username, char *password) {
OSStatus status = SecKeychainAddInternetPassword(
NULL,
strlen(server->host), server->host,
0, NULL,
strlen(username), username,
strlen(server->path), server->path,
server->port,
server->proto,
kSecAuthenticationTypeDefault,
strlen(password), password,
NULL
);
if (status) {
return get_error(status);
}
return NULL;
}
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *password_l, char **password) {
char *tmp;
SecKeychainItemRef item;
OSStatus status = SecKeychainFindInternetPassword(
NULL,
strlen(server->host), server->host,
0, NULL,
0, NULL,
strlen(server->path), server->path,
server->port,
server->proto,
kSecAuthenticationTypeDefault,
password_l, (void **)&tmp,
&item);
if (status) {
return get_error(status);
}
*password = strdup(tmp);
SecKeychainItemFreeContent(NULL, tmp);
SecKeychainAttributeList list;
SecKeychainAttribute attr;
list.count = 1;
list.attr = &attr;
attr.tag = kSecAccountItemAttr;
status = SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL);
if (status) {
return get_error(status);
}
*username = strdup(attr.data);
*username_l = attr.length;
SecKeychainItemFreeContent(&list, NULL);
return NULL;
}
char *keychain_delete(struct Server *server) {
SecKeychainItemRef item;
OSStatus status = SecKeychainFindInternetPassword(
NULL,
strlen(server->host), server->host,
0, NULL,
0, NULL,
strlen(server->path), server->path,
server->port,
server->proto,
kSecAuthenticationTypeDefault,
0, NULL,
&item);
if (status) {
return get_error(status);
}
status = SecKeychainItemDelete(item);
if (status) {
return get_error(status);
}
return NULL;
}
+125
View File
@@ -0,0 +1,125 @@
package osxkeychain
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Security -framework Foundation
#include "osxkeychain_darwin.h"
#include <stdlib.h>
*/
import "C"
import (
"errors"
"net/url"
"strconv"
"strings"
"unsafe"
"github.com/calavera/docker-credential-helpers/credentials"
)
type osxkeychain struct{}
func New() credentials.Helper {
return osxkeychain{}
}
// Add adds new credentials to the keychain.
func (h osxkeychain) Add(creds *credentials.Credentials) error {
s, err := splitServer(creds.ServerURL)
if err != nil {
return err
}
defer freeServer(s)
username := C.CString(creds.Username)
defer C.free(unsafe.Pointer(username))
password := C.CString(creds.Password)
defer C.free(unsafe.Pointer(password))
errMsg := C.keychain_add(s, username, password)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
return errors.New(C.GoString(errMsg))
}
return nil
}
// Delete removes credentials from the keychain.
func (h osxkeychain) Delete(serverURL string) error {
s, err := splitServer(serverURL)
if err != nil {
return err
}
defer freeServer(s)
errMsg := C.keychain_delete(s)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
return errors.New(C.GoString(errMsg))
}
return nil
}
// Get returns the username and password to use for a given registry server URL.
func (h osxkeychain) Get(serverURL string) (string, string, error) {
s, err := splitServer(serverURL)
if err != nil {
return "", "", err
}
defer freeServer(s)
var usernameLen C.uint
var username *C.char
var passwordLen C.uint
var password *C.char
defer C.free(unsafe.Pointer(username))
defer C.free(unsafe.Pointer(password))
errMsg := C.keychain_get(s, &usernameLen, &username, &passwordLen, &password)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
return "", "", errors.New(C.GoString(errMsg))
}
user := C.GoStringN(username, C.int(usernameLen))
pass := C.GoStringN(password, C.int(passwordLen))
return user, pass, nil
}
func splitServer(serverURL string) (*C.struct_Server, error) {
u, err := url.Parse(serverURL)
if err != nil {
return nil, err
}
hostAndPort := strings.Split(u.Host, ":")
host := hostAndPort[0]
var port int
if len(hostAndPort) == 2 {
p, err := strconv.Atoi(hostAndPort[1])
if err != nil {
return nil, err
}
port = p
}
proto := C.kSecProtocolTypeHTTPS
if u.Scheme != "https" {
proto = C.kSecProtocolTypeHTTP
}
return &C.struct_Server{
proto: C.SecProtocolType(proto),
host: C.CString(host),
port: C.uint(port),
path: C.CString(u.Path),
}, nil
}
func freeServer(s *C.struct_Server) {
C.free(unsafe.Pointer(s.host))
C.free(unsafe.Pointer(s.path))
}
+12
View File
@@ -0,0 +1,12 @@
#include <Security/Security.h>
struct Server {
SecProtocolType proto;
char *host;
char *path;
unsigned int port;
};
char *keychain_add(struct Server *server, char *username, char *password);
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *password_l, char **password);
char *keychain_delete(struct Server *server);
+37
View File
@@ -0,0 +1,37 @@
package osxkeychain
import (
"testing"
"github.com/calavera/docker-credential-helpers/credentials"
)
func TestOSXKeychainHelper(t *testing.T) {
creds := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v1",
Username: "foobar",
Password: "foobarbaz",
}
helper := New()
if err := helper.Add(creds); err != nil {
t.Fatal(err)
}
username, password, err := helper.Get(creds.ServerURL)
if err != nil {
t.Fatal(err)
}
if username != "foobar" {
t.Fatalf("expected %s, got %s\n", "foobar", username)
}
if password != "foobarbaz" {
t.Fatalf("expected %s, got %s\n", "foobarbaz", password)
}
if err := helper.Delete(creds.ServerURL); err != nil {
t.Fatal(err)
}
}