credential_darwin.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. //go:build darwin && cgo
  2. package ccm
  3. import (
  4. "crypto/sha256"
  5. "encoding/hex"
  6. "encoding/json"
  7. "os"
  8. "path/filepath"
  9. E "github.com/sagernet/sing/common/exceptions"
  10. "github.com/keybase/go-keychain"
  11. )
  12. func getKeychainServiceName() string {
  13. configDirectory := os.Getenv("CLAUDE_CONFIG_DIR")
  14. if configDirectory == "" {
  15. return "Claude Code-credentials"
  16. }
  17. userInfo, err := getRealUser()
  18. if err != nil {
  19. return "Claude Code-credentials"
  20. }
  21. defaultConfigDirectory := filepath.Join(userInfo.HomeDir, ".claude")
  22. if configDirectory == defaultConfigDirectory {
  23. return "Claude Code-credentials"
  24. }
  25. hash := sha256.Sum256([]byte(configDirectory))
  26. return "Claude Code-credentials-" + hex.EncodeToString(hash[:])[:8]
  27. }
  28. func platformReadCredentials(customPath string) (*oauthCredentials, error) {
  29. if customPath != "" {
  30. return readCredentialsFromFile(customPath)
  31. }
  32. userInfo, err := getRealUser()
  33. if err == nil {
  34. query := keychain.NewItem()
  35. query.SetSecClass(keychain.SecClassGenericPassword)
  36. query.SetService(getKeychainServiceName())
  37. query.SetAccount(userInfo.Username)
  38. query.SetMatchLimit(keychain.MatchLimitOne)
  39. query.SetReturnData(true)
  40. results, err := keychain.QueryItem(query)
  41. if err == nil && len(results) == 1 {
  42. var container struct {
  43. ClaudeAIAuth *oauthCredentials `json:"claudeAiOauth,omitempty"`
  44. }
  45. unmarshalErr := json.Unmarshal(results[0].Data, &container)
  46. if unmarshalErr == nil && container.ClaudeAIAuth != nil {
  47. return container.ClaudeAIAuth, nil
  48. }
  49. }
  50. if err != nil && err != keychain.ErrorItemNotFound {
  51. return nil, E.Cause(err, "query keychain")
  52. }
  53. }
  54. defaultPath, err := getDefaultCredentialsPath()
  55. if err != nil {
  56. return nil, err
  57. }
  58. return readCredentialsFromFile(defaultPath)
  59. }
  60. func platformWriteCredentials(oauthCredentials *oauthCredentials, customPath string) error {
  61. if customPath != "" {
  62. return writeCredentialsToFile(oauthCredentials, customPath)
  63. }
  64. userInfo, err := getRealUser()
  65. if err == nil {
  66. data, err := json.Marshal(map[string]any{"claudeAiOauth": oauthCredentials})
  67. if err == nil {
  68. serviceName := getKeychainServiceName()
  69. item := keychain.NewItem()
  70. item.SetSecClass(keychain.SecClassGenericPassword)
  71. item.SetService(serviceName)
  72. item.SetAccount(userInfo.Username)
  73. item.SetData(data)
  74. item.SetAccessible(keychain.AccessibleWhenUnlocked)
  75. err = keychain.AddItem(item)
  76. if err == nil {
  77. return nil
  78. }
  79. if err == keychain.ErrorDuplicateItem {
  80. query := keychain.NewItem()
  81. query.SetSecClass(keychain.SecClassGenericPassword)
  82. query.SetService(serviceName)
  83. query.SetAccount(userInfo.Username)
  84. updateItem := keychain.NewItem()
  85. updateItem.SetData(data)
  86. updateErr := keychain.UpdateItem(query, updateItem)
  87. if updateErr == nil {
  88. return nil
  89. }
  90. }
  91. }
  92. }
  93. defaultPath, err := getDefaultCredentialsPath()
  94. if err != nil {
  95. return err
  96. }
  97. return writeCredentialsToFile(oauthCredentials, defaultPath)
  98. }