github.com/versent/saml2aws@v2.17.0+incompatible/helper/osxkeychain/osxkeychain_darwin.go (about)

     1  // Copyright (c) 2016 David Calavera
     2  
     3  // Permission is hereby granted, free of charge, to any person obtaining
     4  // a copy of this software and associated documentation files (the
     5  // "Software"), to deal in the Software without restriction, including
     6  // without limitation the rights to use, copy, modify, merge, publish,
     7  // distribute, sublicense, and/or sell copies of the Software, and to
     8  // permit persons to whom the Software is furnished to do so, subject to
     9  // the following conditions:
    10  
    11  // The above copyright notice and this permission notice shall be
    12  // included in all copies or substantial portions of the Software.
    13  
    14  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    15  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    16  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    17  // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    18  // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    19  // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    20  // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    21  //
    22  // https://github.com/docker/docker-credential-helpers
    23  package osxkeychain
    24  
    25  /*
    26  #cgo CFLAGS: -x objective-c -mmacosx-version-min=10.10
    27  #cgo LDFLAGS: -framework Security -framework Foundation
    28  
    29  #include "osxkeychain_darwin.h"
    30  #include <stdlib.h>
    31  */
    32  import "C"
    33  import (
    34  	"errors"
    35  	"net/url"
    36  	"strconv"
    37  	"strings"
    38  	"unsafe"
    39  
    40  	"github.com/sirupsen/logrus"
    41  	"github.com/versent/saml2aws/helper/credentials"
    42  )
    43  
    44  var logger = logrus.WithField("helper", "osxkeychain")
    45  
    46  // errCredentialsNotFound is the specific error message returned by OS X
    47  // when the credentials are not in the keychain.
    48  const errCredentialsNotFound = "The specified item could not be found in the keychain."
    49  
    50  // Osxkeychain handles secrets using the OS X Keychain as store.
    51  type Osxkeychain struct{}
    52  
    53  // Add adds new credentials to the keychain.
    54  func (h Osxkeychain) Add(creds *credentials.Credentials) error {
    55  	h.Delete(creds.ServerURL)
    56  
    57  	s, err := splitServer(creds.ServerURL)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	defer freeServer(s)
    62  
    63  	label := C.CString(credentials.CredsLabel)
    64  	defer C.free(unsafe.Pointer(label))
    65  	username := C.CString(creds.Username)
    66  	defer C.free(unsafe.Pointer(username))
    67  	secret := C.CString(creds.Secret)
    68  	defer C.free(unsafe.Pointer(secret))
    69  
    70  	errMsg := C.keychain_add(s, label, username, secret)
    71  	if errMsg != nil {
    72  		defer C.free(unsafe.Pointer(errMsg))
    73  		return errors.New(C.GoString(errMsg))
    74  	}
    75  
    76  	return nil
    77  }
    78  
    79  // Delete removes credentials from the keychain.
    80  func (h Osxkeychain) Delete(serverURL string) error {
    81  	s, err := splitServer(serverURL)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	defer freeServer(s)
    86  
    87  	errMsg := C.keychain_delete(s)
    88  	if errMsg != nil {
    89  		defer C.free(unsafe.Pointer(errMsg))
    90  		return errors.New(C.GoString(errMsg))
    91  	}
    92  
    93  	return nil
    94  }
    95  
    96  // Get returns the username and secret to use for a given registry server URL.
    97  func (h Osxkeychain) Get(serverURL string) (string, string, error) {
    98  
    99  	logger.WithField("serverURL", serverURL).Debug("Get credentials")
   100  
   101  	s, err := splitServer(serverURL)
   102  	if err != nil {
   103  		return "", "", err
   104  	}
   105  	defer freeServer(s)
   106  
   107  	var usernameLen C.uint
   108  	var username *C.char
   109  	var secretLen C.uint
   110  	var secret *C.char
   111  	defer C.free(unsafe.Pointer(username))
   112  	defer C.free(unsafe.Pointer(secret))
   113  
   114  	errMsg := C.keychain_get(s, &usernameLen, &username, &secretLen, &secret)
   115  	if errMsg != nil {
   116  		defer C.free(unsafe.Pointer(errMsg))
   117  		goMsg := C.GoString(errMsg)
   118  
   119  		if goMsg == errCredentialsNotFound {
   120  			logger.WithField("goMsg", goMsg).Debug("Get credentials")
   121  			return "", "", credentials.ErrCredentialsNotFound
   122  		}
   123  
   124  		logger.WithField("goMsg", goMsg).Error("keychain Get returned error")
   125  		return "", "", errors.New(goMsg)
   126  	}
   127  
   128  	user := C.GoStringN(username, C.int(usernameLen))
   129  	pass := C.GoStringN(secret, C.int(secretLen))
   130  
   131  	logger.WithField("user", user).Debug("Get credentials")
   132  
   133  	return user, pass, nil
   134  }
   135  
   136  // List returns the stored URLs and corresponding usernames.
   137  func (h Osxkeychain) List() (map[string]string, error) {
   138  	credsLabelC := C.CString(credentials.CredsLabel)
   139  	defer C.free(unsafe.Pointer(credsLabelC))
   140  
   141  	var pathsC **C.char
   142  	defer C.free(unsafe.Pointer(pathsC))
   143  	var acctsC **C.char
   144  	defer C.free(unsafe.Pointer(acctsC))
   145  	var listLenC C.uint
   146  	errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC)
   147  	if errMsg != nil {
   148  		defer C.free(unsafe.Pointer(errMsg))
   149  		goMsg := C.GoString(errMsg)
   150  		return nil, errors.New(goMsg)
   151  	}
   152  
   153  	defer C.freeListData(&pathsC, listLenC)
   154  	defer C.freeListData(&acctsC, listLenC)
   155  
   156  	var listLen int
   157  	listLen = int(listLenC)
   158  	pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
   159  	acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
   160  	//taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper
   161  	resp := make(map[string]string)
   162  	for i := 0; i < listLen; i++ {
   163  		if C.GoString(pathTmp[i]) == "0" {
   164  			continue
   165  		}
   166  		resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
   167  	}
   168  	return resp, nil
   169  }
   170  
   171  // SupportsCredentialsStorage returns true since storage is supported
   172  func (Osxkeychain) SupportsCredentialStorage() bool {
   173  	return true
   174  }
   175  
   176  func splitServer(serverURL string) (*C.struct_Server, error) {
   177  	u, err := url.Parse(serverURL)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	hostAndPort := strings.Split(u.Host, ":")
   183  	host := hostAndPort[0]
   184  	var port int
   185  	if len(hostAndPort) == 2 {
   186  		p, err := strconv.Atoi(hostAndPort[1])
   187  		if err != nil {
   188  			return nil, err
   189  		}
   190  		port = p
   191  	}
   192  
   193  	proto := C.kSecProtocolTypeHTTPS
   194  	if u.Scheme != "https" {
   195  		proto = C.kSecProtocolTypeHTTP
   196  	}
   197  
   198  	return &C.struct_Server{
   199  		proto: C.SecProtocolType(proto),
   200  		host:  C.CString(host),
   201  		port:  C.uint(port),
   202  		path:  C.CString(u.Path),
   203  	}, nil
   204  }
   205  
   206  func freeServer(s *C.struct_Server) {
   207  	C.free(unsafe.Pointer(s.host))
   208  	C.free(unsafe.Pointer(s.path))
   209  }