...

Source file src/github.com/docker/docker-credential-helpers/osxkeychain/osxkeychain.go

Documentation: github.com/docker/docker-credential-helpers/osxkeychain

     1  //go:build darwin && cgo
     2  
     3  package osxkeychain
     4  
     5  /*
     6  #cgo CFLAGS: -x objective-c
     7  #cgo LDFLAGS: -framework Security -framework Foundation
     8  
     9  #include "osxkeychain.h"
    10  #include <stdlib.h>
    11  */
    12  import "C"
    13  
    14  import (
    15  	"errors"
    16  	"strconv"
    17  	"unsafe"
    18  
    19  	"github.com/docker/docker-credential-helpers/credentials"
    20  	"github.com/docker/docker-credential-helpers/registryurl"
    21  )
    22  
    23  // errCredentialsNotFound is the specific error message returned by OS X
    24  // when the credentials are not in the keychain.
    25  const errCredentialsNotFound = "The specified item could not be found in the keychain."
    26  
    27  // errCredentialsNotFound is the specific error message returned by OS X
    28  // when environment does not allow showing dialog to unlock keychain.
    29  const errInteractionNotAllowed = "User interaction is not allowed."
    30  
    31  // ErrInteractionNotAllowed is returned if keychain password prompt can not be shown.
    32  var ErrInteractionNotAllowed = errors.New(`keychain cannot be accessed because the current session does not allow user interaction. The keychain may be locked; unlock it by running "security -v unlock-keychain ~/Library/Keychains/login.keychain-db" and try again`)
    33  
    34  // Osxkeychain handles secrets using the OS X Keychain as store.
    35  type Osxkeychain struct{}
    36  
    37  // Add adds new credentials to the keychain.
    38  func (h Osxkeychain) Add(creds *credentials.Credentials) error {
    39  	_ = h.Delete(creds.ServerURL) // ignore errors as existing credential may not exist.
    40  
    41  	s, err := splitServer(creds.ServerURL)
    42  	if err != nil {
    43  		return err
    44  	}
    45  	defer freeServer(s)
    46  
    47  	label := C.CString(credentials.CredsLabel)
    48  	defer C.free(unsafe.Pointer(label))
    49  	username := C.CString(creds.Username)
    50  	defer C.free(unsafe.Pointer(username))
    51  	secret := C.CString(creds.Secret)
    52  	defer C.free(unsafe.Pointer(secret))
    53  
    54  	errMsg := C.keychain_add(s, label, username, secret)
    55  	if errMsg != nil {
    56  		defer C.free(unsafe.Pointer(errMsg))
    57  		return errors.New(C.GoString(errMsg))
    58  	}
    59  
    60  	return nil
    61  }
    62  
    63  // Delete removes credentials from the keychain.
    64  func (h Osxkeychain) Delete(serverURL string) error {
    65  	s, err := splitServer(serverURL)
    66  	if err != nil {
    67  		return err
    68  	}
    69  	defer freeServer(s)
    70  
    71  	if errMsg := C.keychain_delete(s); errMsg != nil {
    72  		defer C.free(unsafe.Pointer(errMsg))
    73  		switch goMsg := C.GoString(errMsg); goMsg {
    74  		case errCredentialsNotFound:
    75  			return credentials.NewErrCredentialsNotFound()
    76  		case errInteractionNotAllowed:
    77  			return ErrInteractionNotAllowed
    78  		default:
    79  			return errors.New(goMsg)
    80  		}
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  // Get returns the username and secret to use for a given registry server URL.
    87  func (h Osxkeychain) Get(serverURL string) (string, string, error) {
    88  	s, err := splitServer(serverURL)
    89  	if err != nil {
    90  		return "", "", err
    91  	}
    92  	defer freeServer(s)
    93  
    94  	var usernameLen C.uint
    95  	var username *C.char
    96  	var secretLen C.uint
    97  	var secret *C.char
    98  	defer C.free(unsafe.Pointer(username))
    99  	defer C.free(unsafe.Pointer(secret))
   100  
   101  	errMsg := C.keychain_get(s, &usernameLen, &username, &secretLen, &secret)
   102  	if errMsg != nil {
   103  		defer C.free(unsafe.Pointer(errMsg))
   104  		switch goMsg := C.GoString(errMsg); goMsg {
   105  		case errCredentialsNotFound:
   106  			return "", "", credentials.NewErrCredentialsNotFound()
   107  		case errInteractionNotAllowed:
   108  			return "", "", ErrInteractionNotAllowed
   109  		default:
   110  			return "", "", errors.New(goMsg)
   111  		}
   112  	}
   113  
   114  	user := C.GoStringN(username, C.int(usernameLen))
   115  	pass := C.GoStringN(secret, C.int(secretLen))
   116  	return user, pass, nil
   117  }
   118  
   119  // List returns the stored URLs and corresponding usernames.
   120  func (h Osxkeychain) List() (map[string]string, error) {
   121  	credsLabelC := C.CString(credentials.CredsLabel)
   122  	defer C.free(unsafe.Pointer(credsLabelC))
   123  
   124  	var pathsC **C.char
   125  	defer C.free(unsafe.Pointer(pathsC))
   126  	var acctsC **C.char
   127  	defer C.free(unsafe.Pointer(acctsC))
   128  	var listLenC C.uint
   129  	errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC)
   130  	defer C.freeListData(&pathsC, listLenC)
   131  	defer C.freeListData(&acctsC, listLenC)
   132  	if errMsg != nil {
   133  		defer C.free(unsafe.Pointer(errMsg))
   134  		switch goMsg := C.GoString(errMsg); goMsg {
   135  		case errCredentialsNotFound:
   136  			return make(map[string]string), nil
   137  		case errInteractionNotAllowed:
   138  			return nil, ErrInteractionNotAllowed
   139  		default:
   140  			return nil, errors.New(goMsg)
   141  		}
   142  	}
   143  
   144  	var listLen int
   145  	listLen = int(listLenC)
   146  	pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
   147  	acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
   148  	// taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper
   149  	resp := make(map[string]string)
   150  	for i := 0; i < listLen; i++ {
   151  		if C.GoString(pathTmp[i]) == "0" {
   152  			continue
   153  		}
   154  		resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
   155  	}
   156  	return resp, nil
   157  }
   158  
   159  func splitServer(serverURL string) (*C.struct_Server, error) {
   160  	u, err := registryurl.Parse(serverURL)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	proto := C.kSecProtocolTypeHTTPS
   166  	if u.Scheme == "http" {
   167  		proto = C.kSecProtocolTypeHTTP
   168  	}
   169  	var port int
   170  	p := u.Port()
   171  	if p != "" {
   172  		port, err = strconv.Atoi(p)
   173  		if err != nil {
   174  			return nil, err
   175  		}
   176  	}
   177  
   178  	return &C.struct_Server{
   179  		proto: C.SecProtocolType(proto),
   180  		host:  C.CString(u.Hostname()),
   181  		port:  C.uint(port),
   182  		path:  C.CString(u.Path),
   183  	}, nil
   184  }
   185  
   186  func freeServer(s *C.struct_Server) {
   187  	C.free(unsafe.Pointer(s.host))
   188  	C.free(unsafe.Pointer(s.path))
   189  }
   190  

View as plain text