helper/osxkeychain/osxkeychain_darwin.go (138 lines of code) (raw):
// Copyright (c) 2016 David Calavera
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// https://github.com/docker/docker-credential-helpers
package osxkeychain
/*
#cgo CFLAGS: -x objective-c -mmacosx-version-min=10.10
#cgo LDFLAGS: -framework Security -framework Foundation
#include "osxkeychain_darwin.h"
#include <stdlib.h>
*/
import "C"
import (
"errors"
"net/url"
"strconv"
"strings"
"unsafe"
"github.com/aliyun/saml2alibabacloud/helper/credentials"
"github.com/sirupsen/logrus"
)
var logger = logrus.WithField("helper", "osxkeychain")
// errCredentialsNotFound is the specific error message returned by OS X
// when the credentials are not in the keychain.
const errCredentialsNotFound = "The specified item could not be found in the keychain."
// Osxkeychain handles secrets using the OS X Keychain as store.
type Osxkeychain struct{}
// Add adds new credentials to the keychain.
func (h Osxkeychain) Add(creds *credentials.Credentials) error {
h.Delete(creds.ServerURL)
s, err := splitServer(creds.ServerURL)
if err != nil {
return err
}
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, label, username, secret)
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 secret to use for a given registry server URL.
func (h Osxkeychain) Get(serverURL string) (string, string, error) {
logger.WithField("serverURL", serverURL).Debug("Get credentials")
s, err := splitServer(serverURL)
if err != nil {
return "", "", err
}
defer freeServer(s)
var usernameLen C.uint
var username *C.char
var secretLen C.uint
var secret *C.char
defer C.free(unsafe.Pointer(username))
defer C.free(unsafe.Pointer(secret))
errMsg := C.keychain_get(s, &usernameLen, &username, &secretLen, &secret)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
goMsg := C.GoString(errMsg)
if goMsg == errCredentialsNotFound {
logger.WithField("goMsg", goMsg).Debug("Get credentials")
return "", "", credentials.ErrCredentialsNotFound
}
logger.WithField("goMsg", goMsg).Error("keychain Get returned error")
return "", "", errors.New(goMsg)
}
user := C.GoStringN(username, C.int(usernameLen))
pass := C.GoStringN(secret, C.int(secretLen))
logger.WithField("user", user).Debug("Get credentials")
return user, pass, nil
}
// 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(credsLabelC, &pathsC, &acctsC, &listLenC)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
goMsg := C.GoString(errMsg)
return nil, errors.New(goMsg)
}
defer C.freeListData(&pathsC, listLenC)
defer C.freeListData(&acctsC, listLenC)
var listLen int
listLen = int(listLenC)
pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
//taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper
resp := make(map[string]string)
for i := 0; i < listLen; i++ {
if C.GoString(pathTmp[i]) == "0" {
continue
}
resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
}
return resp, nil
}
// SupportsCredentialsStorage returns true since storage is supported
func (Osxkeychain) SupportsCredentialStorage() bool {
return true
}
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))
}