osxkeychain_darwin.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. package osxkeychain
  2. /*
  3. #cgo CFLAGS: -x objective-c -mmacosx-version-min=10.10
  4. #cgo LDFLAGS: -framework Security -framework Foundation -mmacosx-version-min=10.10
  5. #include "osxkeychain_darwin.h"
  6. #include <stdlib.h>
  7. */
  8. import "C"
  9. import (
  10. "errors"
  11. "net/url"
  12. "strconv"
  13. "strings"
  14. "unsafe"
  15. "github.com/docker/docker-credential-helpers/credentials"
  16. )
  17. // errCredentialsNotFound is the specific error message returned by OS X
  18. // when the credentials are not in the keychain.
  19. const errCredentialsNotFound = "The specified item could not be found in the keychain."
  20. // Osxkeychain handles secrets using the OS X Keychain as store.
  21. type Osxkeychain struct{}
  22. // Add adds new credentials to the keychain.
  23. func (h Osxkeychain) Add(creds *credentials.Credentials) error {
  24. h.Delete(creds.ServerURL)
  25. s, err := splitServer(creds.ServerURL)
  26. if err != nil {
  27. return err
  28. }
  29. defer freeServer(s)
  30. label := C.CString(credentials.CredsLabel)
  31. defer C.free(unsafe.Pointer(label))
  32. username := C.CString(creds.Username)
  33. defer C.free(unsafe.Pointer(username))
  34. secret := C.CString(creds.Secret)
  35. defer C.free(unsafe.Pointer(secret))
  36. errMsg := C.keychain_add(s, label, username, secret)
  37. if errMsg != nil {
  38. defer C.free(unsafe.Pointer(errMsg))
  39. return errors.New(C.GoString(errMsg))
  40. }
  41. return nil
  42. }
  43. // Delete removes credentials from the keychain.
  44. func (h Osxkeychain) Delete(serverURL string) error {
  45. s, err := splitServer(serverURL)
  46. if err != nil {
  47. return err
  48. }
  49. defer freeServer(s)
  50. errMsg := C.keychain_delete(s)
  51. if errMsg != nil {
  52. defer C.free(unsafe.Pointer(errMsg))
  53. return errors.New(C.GoString(errMsg))
  54. }
  55. return nil
  56. }
  57. // Get returns the username and secret to use for a given registry server URL.
  58. func (h Osxkeychain) Get(serverURL string) (string, string, error) {
  59. s, err := splitServer(serverURL)
  60. if err != nil {
  61. return "", "", err
  62. }
  63. defer freeServer(s)
  64. var usernameLen C.uint
  65. var username *C.char
  66. var secretLen C.uint
  67. var secret *C.char
  68. defer C.free(unsafe.Pointer(username))
  69. defer C.free(unsafe.Pointer(secret))
  70. errMsg := C.keychain_get(s, &usernameLen, &username, &secretLen, &secret)
  71. if errMsg != nil {
  72. defer C.free(unsafe.Pointer(errMsg))
  73. goMsg := C.GoString(errMsg)
  74. if goMsg == errCredentialsNotFound {
  75. return "", "", credentials.NewErrCredentialsNotFound()
  76. }
  77. return "", "", errors.New(goMsg)
  78. }
  79. user := C.GoStringN(username, C.int(usernameLen))
  80. pass := C.GoStringN(secret, C.int(secretLen))
  81. return user, pass, nil
  82. }
  83. // List returns the stored URLs and corresponding usernames.
  84. func (h Osxkeychain) List() (map[string]string, error) {
  85. credsLabelC := C.CString(credentials.CredsLabel)
  86. defer C.free(unsafe.Pointer(credsLabelC))
  87. var pathsC **C.char
  88. defer C.free(unsafe.Pointer(pathsC))
  89. var acctsC **C.char
  90. defer C.free(unsafe.Pointer(acctsC))
  91. var listLenC C.uint
  92. errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC)
  93. if errMsg != nil {
  94. defer C.free(unsafe.Pointer(errMsg))
  95. goMsg := C.GoString(errMsg)
  96. return nil, errors.New(goMsg)
  97. }
  98. defer C.freeListData(&pathsC, listLenC)
  99. defer C.freeListData(&acctsC, listLenC)
  100. var listLen int
  101. listLen = int(listLenC)
  102. pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
  103. acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
  104. //taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper
  105. resp := make(map[string]string)
  106. for i := 0; i < listLen; i++ {
  107. if C.GoString(pathTmp[i]) == "0" {
  108. continue
  109. }
  110. resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
  111. }
  112. return resp, nil
  113. }
  114. func splitServer(serverURL string) (*C.struct_Server, error) {
  115. u, err := url.Parse(serverURL)
  116. if err != nil {
  117. return nil, err
  118. }
  119. hostAndPort := strings.Split(u.Host, ":")
  120. host := hostAndPort[0]
  121. var port int
  122. if len(hostAndPort) == 2 {
  123. p, err := strconv.Atoi(hostAndPort[1])
  124. if err != nil {
  125. return nil, err
  126. }
  127. port = p
  128. }
  129. proto := C.kSecProtocolTypeHTTPS
  130. if u.Scheme != "https" {
  131. proto = C.kSecProtocolTypeHTTP
  132. }
  133. return &C.struct_Server{
  134. proto: C.SecProtocolType(proto),
  135. host: C.CString(host),
  136. port: C.uint(port),
  137. path: C.CString(u.Path),
  138. }, nil
  139. }
  140. func freeServer(s *C.struct_Server) {
  141. C.free(unsafe.Pointer(s.host))
  142. C.free(unsafe.Pointer(s.path))
  143. }