mirror of
https://github.com/docker/docker-credential-helpers.git
synced 2026-06-13 16:01:28 +05:30
osxkeychain: switch to github.com/keybase/go-keychain
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
+26
@@ -0,0 +1,26 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
vendor
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
linters-settings:
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
- elseif
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gofmt
|
||||
- gocritic
|
||||
- unconvert
|
||||
- revive
|
||||
- govet
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Keybase
|
||||
|
||||
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.
|
||||
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
# Go Keychain
|
||||
|
||||
[](https://github.com/keybase/go-keychain/actions)
|
||||
|
||||
A library for accessing the Keychain for macOS, iOS, and Linux in Go (golang).
|
||||
|
||||
Requires macOS 10.9 or greater and iOS 8 or greater. On Linux, communicates to
|
||||
a provider of the DBUS SecretService spec like gnome-keyring or ksecretservice.
|
||||
|
||||
```go
|
||||
import "github.com/keybase/go-keychain"
|
||||
```
|
||||
|
||||
## Mac/iOS Usage
|
||||
|
||||
The API is meant to mirror the macOS/iOS Keychain API and is not necessarily idiomatic go.
|
||||
|
||||
#### Add Item
|
||||
|
||||
```go
|
||||
item := keychain.NewItem()
|
||||
item.SetSecClass(keychain.SecClassGenericPassword)
|
||||
item.SetService("MyService")
|
||||
item.SetAccount("gabriel")
|
||||
item.SetLabel("A label")
|
||||
item.SetAccessGroup("A123456789.group.com.mycorp")
|
||||
item.SetData([]byte("toomanysecrets"))
|
||||
item.SetSynchronizable(keychain.SynchronizableNo)
|
||||
item.SetAccessible(keychain.AccessibleWhenUnlocked)
|
||||
err := keychain.AddItem(item)
|
||||
|
||||
if err == keychain.ErrorDuplicateItem {
|
||||
// Duplicate
|
||||
}
|
||||
```
|
||||
|
||||
#### Query Item
|
||||
|
||||
Query for multiple results, returning attributes:
|
||||
|
||||
```go
|
||||
query := keychain.NewItem()
|
||||
query.SetSecClass(keychain.SecClassGenericPassword)
|
||||
query.SetService(service)
|
||||
query.SetAccount(account)
|
||||
query.SetAccessGroup(accessGroup)
|
||||
query.SetMatchLimit(keychain.MatchLimitAll)
|
||||
query.SetReturnAttributes(true)
|
||||
results, err := keychain.QueryItem(query)
|
||||
if err != nil {
|
||||
// Error
|
||||
} else {
|
||||
for _, r := range results {
|
||||
fmt.Printf("%#v\n", r)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Query for a single result, returning data:
|
||||
|
||||
```go
|
||||
query := keychain.NewItem()
|
||||
query.SetSecClass(keychain.SecClassGenericPassword)
|
||||
query.SetService(service)
|
||||
query.SetAccount(account)
|
||||
query.SetAccessGroup(accessGroup)
|
||||
query.SetMatchLimit(keychain.MatchLimitOne)
|
||||
query.SetReturnData(true)
|
||||
results, err := keychain.QueryItem(query)
|
||||
if err != nil {
|
||||
// Error
|
||||
} else if len(results) != 1 {
|
||||
// Not found
|
||||
} else {
|
||||
password := string(results[0].Data)
|
||||
}
|
||||
```
|
||||
|
||||
#### Delete Item
|
||||
|
||||
Delete a generic password item with service and account:
|
||||
|
||||
```go
|
||||
item := keychain.NewItem()
|
||||
item.SetSecClass(keychain.SecClassGenericPassword)
|
||||
item.SetService(service)
|
||||
item.SetAccount(account)
|
||||
err := keychain.DeleteItem(item)
|
||||
```
|
||||
|
||||
### Other
|
||||
|
||||
There are some convenience methods for generic password:
|
||||
|
||||
```go
|
||||
// Create generic password item with service, account, label, password, access group
|
||||
item := keychain.NewGenericPassword("MyService", "gabriel", "A label", []byte("toomanysecrets"), "A123456789.group.com.mycorp")
|
||||
item.SetSynchronizable(keychain.SynchronizableNo)
|
||||
item.SetAccessible(keychain.AccessibleWhenUnlocked)
|
||||
err := keychain.AddItem(item)
|
||||
if err == keychain.ErrorDuplicateItem {
|
||||
// Duplicate
|
||||
}
|
||||
|
||||
password, err := keychain.GetGenericPassword("MyService", "gabriel", "A label", "A123456789.group.com.mycorp")
|
||||
|
||||
accounts, err := keychain.GetGenericPasswordAccounts("MyService")
|
||||
// Should have 1 account == "gabriel"
|
||||
|
||||
err := keychain.DeleteGenericPasswordItem("MyService", "gabriel")
|
||||
if err == keychain.ErrorItemNotFound {
|
||||
// Not found
|
||||
}
|
||||
```
|
||||
|
||||
## iOS
|
||||
|
||||
Bindable package in `bind`. iOS project in `ios`. Run that project to test iOS.
|
||||
|
||||
To re-generate framework:
|
||||
|
||||
```
|
||||
(cd bind && gomobile bind -target=ios -tags=ios -o ../ios/bind.framework)
|
||||
```
|
||||
|
||||
Post issues to: https://github.com/keybase/keybase-issues
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
//go:build darwin || ios
|
||||
// +build darwin ios
|
||||
|
||||
package keychain
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -framework CoreFoundation
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
// Can't cast a *uintptr to *unsafe.Pointer in Go, and casting
|
||||
// C.CFTypeRef to unsafe.Pointer is unsafe in Go, so have shim functions to
|
||||
// do the casting in C (where it's safe).
|
||||
|
||||
// We add a suffix to the C functions below, because we copied this
|
||||
// file from go-kext, which means that any project that depends on this
|
||||
// package and go-kext would run into duplicate symbol errors otherwise.
|
||||
//
|
||||
// TODO: Move this file into its own package depended on by go-kext
|
||||
// and this package.
|
||||
|
||||
CFDictionaryRef CFDictionaryCreateSafe2(CFAllocatorRef allocator, const uintptr_t *keys, const uintptr_t *values, CFIndex numValues, const CFDictionaryKeyCallBacks *keyCallBacks, const CFDictionaryValueCallBacks *valueCallBacks) {
|
||||
return CFDictionaryCreate(allocator, (const void **)keys, (const void **)values, numValues, keyCallBacks, valueCallBacks);
|
||||
}
|
||||
|
||||
CFArrayRef CFArrayCreateSafe2(CFAllocatorRef allocator, const uintptr_t *values, CFIndex numValues, const CFArrayCallBacks *callBacks) {
|
||||
return CFArrayCreate(allocator, (const void **)values, numValues, callBacks);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Release releases memory pointed to by a CFTypeRef.
|
||||
func Release(ref C.CFTypeRef) {
|
||||
C.CFRelease(ref)
|
||||
}
|
||||
|
||||
// BytesToCFData will return a CFDataRef and if non-nil, must be released with
|
||||
// Release(ref).
|
||||
func BytesToCFData(b []byte) (C.CFDataRef, error) {
|
||||
if uint64(len(b)) > math.MaxUint32 {
|
||||
return 0, errors.New("Data is too large")
|
||||
}
|
||||
var p *C.UInt8
|
||||
if len(b) > 0 {
|
||||
p = (*C.UInt8)(&b[0])
|
||||
}
|
||||
cfData := C.CFDataCreate(C.kCFAllocatorDefault, p, C.CFIndex(len(b)))
|
||||
if cfData == 0 {
|
||||
return 0, fmt.Errorf("CFDataCreate failed")
|
||||
}
|
||||
return cfData, nil
|
||||
}
|
||||
|
||||
// CFDataToBytes converts CFData to bytes.
|
||||
func CFDataToBytes(cfData C.CFDataRef) ([]byte, error) {
|
||||
return C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(cfData)), C.int(C.CFDataGetLength(cfData))), nil
|
||||
}
|
||||
|
||||
// MapToCFDictionary will return a CFDictionaryRef and if non-nil, must be
|
||||
// released with Release(ref).
|
||||
func MapToCFDictionary(m map[C.CFTypeRef]C.CFTypeRef) (C.CFDictionaryRef, error) {
|
||||
var keys, values []C.uintptr_t
|
||||
for key, value := range m {
|
||||
keys = append(keys, C.uintptr_t(key))
|
||||
values = append(values, C.uintptr_t(value))
|
||||
}
|
||||
numValues := len(values)
|
||||
var keysPointer, valuesPointer *C.uintptr_t
|
||||
if numValues > 0 {
|
||||
keysPointer = &keys[0]
|
||||
valuesPointer = &values[0]
|
||||
}
|
||||
cfDict := C.CFDictionaryCreateSafe2(C.kCFAllocatorDefault, keysPointer, valuesPointer, C.CFIndex(numValues),
|
||||
&C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks) //nolint
|
||||
if cfDict == 0 {
|
||||
return 0, fmt.Errorf("CFDictionaryCreate failed")
|
||||
}
|
||||
return cfDict, nil
|
||||
}
|
||||
|
||||
// CFDictionaryToMap converts CFDictionaryRef to a map.
|
||||
func CFDictionaryToMap(cfDict C.CFDictionaryRef) (m map[C.CFTypeRef]C.CFTypeRef) {
|
||||
count := C.CFDictionaryGetCount(cfDict)
|
||||
if count > 0 {
|
||||
keys := make([]C.CFTypeRef, count)
|
||||
values := make([]C.CFTypeRef, count)
|
||||
C.CFDictionaryGetKeysAndValues(cfDict, (*unsafe.Pointer)(unsafe.Pointer(&keys[0])), (*unsafe.Pointer)(unsafe.Pointer(&values[0])))
|
||||
m = make(map[C.CFTypeRef]C.CFTypeRef, count)
|
||||
for i := C.CFIndex(0); i < count; i++ {
|
||||
m[keys[i]] = values[i]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Int32ToCFNumber will return a CFNumberRef, must be released with Release(ref).
|
||||
func Int32ToCFNumber(u int32) C.CFNumberRef {
|
||||
sint := C.SInt32(u)
|
||||
p := unsafe.Pointer(&sint)
|
||||
return C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberSInt32Type, p)
|
||||
}
|
||||
|
||||
// StringToCFString will return a CFStringRef and if non-nil, must be released with
|
||||
// Release(ref).
|
||||
func StringToCFString(s string) (C.CFStringRef, error) {
|
||||
if !utf8.ValidString(s) {
|
||||
return 0, errors.New("Invalid UTF-8 string")
|
||||
}
|
||||
if uint64(len(s)) > math.MaxUint32 {
|
||||
return 0, errors.New("String is too large")
|
||||
}
|
||||
|
||||
bytes := []byte(s)
|
||||
var p *C.UInt8
|
||||
if len(bytes) > 0 {
|
||||
p = (*C.UInt8)(&bytes[0])
|
||||
}
|
||||
return C.CFStringCreateWithBytes(C.kCFAllocatorDefault, p, C.CFIndex(len(s)), C.kCFStringEncodingUTF8, C.false), nil
|
||||
}
|
||||
|
||||
// CFStringToString converts a CFStringRef to a string.
|
||||
func CFStringToString(s C.CFStringRef) string {
|
||||
p := C.CFStringGetCStringPtr(s, C.kCFStringEncodingUTF8)
|
||||
if p != nil {
|
||||
return C.GoString(p)
|
||||
}
|
||||
length := C.CFStringGetLength(s)
|
||||
if length == 0 {
|
||||
return ""
|
||||
}
|
||||
maxBufLen := C.CFStringGetMaximumSizeForEncoding(length, C.kCFStringEncodingUTF8)
|
||||
if maxBufLen == 0 {
|
||||
return ""
|
||||
}
|
||||
buf := make([]byte, maxBufLen)
|
||||
var usedBufLen C.CFIndex
|
||||
_ = C.CFStringGetBytes(s, C.CFRange{0, length}, C.kCFStringEncodingUTF8, C.UInt8(0), C.false, (*C.UInt8)(&buf[0]), maxBufLen, &usedBufLen)
|
||||
return string(buf[:usedBufLen])
|
||||
}
|
||||
|
||||
// ArrayToCFArray will return a CFArrayRef and if non-nil, must be released with
|
||||
// Release(ref).
|
||||
func ArrayToCFArray(a []C.CFTypeRef) C.CFArrayRef {
|
||||
var values []C.uintptr_t
|
||||
for _, value := range a {
|
||||
values = append(values, C.uintptr_t(value))
|
||||
}
|
||||
numValues := len(values)
|
||||
var valuesPointer *C.uintptr_t
|
||||
if numValues > 0 {
|
||||
valuesPointer = &values[0]
|
||||
}
|
||||
return C.CFArrayCreateSafe2(C.kCFAllocatorDefault, valuesPointer, C.CFIndex(numValues), &C.kCFTypeArrayCallBacks) //nolint
|
||||
}
|
||||
|
||||
// CFArrayToArray converts a CFArrayRef to an array of CFTypes.
|
||||
func CFArrayToArray(cfArray C.CFArrayRef) (a []C.CFTypeRef) {
|
||||
count := C.CFArrayGetCount(cfArray)
|
||||
if count > 0 {
|
||||
a = make([]C.CFTypeRef, count)
|
||||
C.CFArrayGetValues(cfArray, C.CFRange{0, count}, (*unsafe.Pointer)(unsafe.Pointer(&a[0])))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Convertable knows how to convert an instance to a CFTypeRef.
|
||||
type Convertable interface {
|
||||
Convert() (C.CFTypeRef, error)
|
||||
}
|
||||
|
||||
// ConvertMapToCFDictionary converts a map to a CFDictionary and if non-nil,
|
||||
// must be released with Release(ref).
|
||||
func ConvertMapToCFDictionary(attr map[string]interface{}) (C.CFDictionaryRef, error) {
|
||||
m := make(map[C.CFTypeRef]C.CFTypeRef)
|
||||
for key, i := range attr {
|
||||
var valueRef C.CFTypeRef
|
||||
switch val := i.(type) {
|
||||
default:
|
||||
return 0, fmt.Errorf("Unsupported value type: %v", reflect.TypeOf(i))
|
||||
case C.CFTypeRef:
|
||||
valueRef = val
|
||||
case bool:
|
||||
if val {
|
||||
valueRef = C.CFTypeRef(C.kCFBooleanTrue)
|
||||
} else {
|
||||
valueRef = C.CFTypeRef(C.kCFBooleanFalse)
|
||||
}
|
||||
case int32:
|
||||
valueRef = C.CFTypeRef(Int32ToCFNumber(val))
|
||||
defer Release(valueRef)
|
||||
case []byte:
|
||||
bytesRef, err := BytesToCFData(val)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
valueRef = C.CFTypeRef(bytesRef)
|
||||
defer Release(valueRef)
|
||||
case string:
|
||||
stringRef, err := StringToCFString(val)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
valueRef = C.CFTypeRef(stringRef)
|
||||
defer Release(valueRef)
|
||||
case Convertable:
|
||||
convertedRef, err := val.Convert()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
valueRef = convertedRef
|
||||
defer Release(valueRef)
|
||||
}
|
||||
keyRef, err := StringToCFString(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
m[C.CFTypeRef(keyRef)] = valueRef
|
||||
defer Release(C.CFTypeRef(keyRef))
|
||||
}
|
||||
|
||||
cfDict, err := MapToCFDictionary(m)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return cfDict, nil
|
||||
}
|
||||
|
||||
// CFTypeDescription returns type string for CFTypeRef.
|
||||
func CFTypeDescription(ref C.CFTypeRef) string {
|
||||
typeID := C.CFGetTypeID(ref)
|
||||
typeDesc := C.CFCopyTypeIDDescription(typeID)
|
||||
defer Release(C.CFTypeRef(typeDesc))
|
||||
return CFStringToString(typeDesc)
|
||||
}
|
||||
|
||||
// Convert converts a CFTypeRef to a go instance.
|
||||
func Convert(ref C.CFTypeRef) (interface{}, error) {
|
||||
typeID := C.CFGetTypeID(ref)
|
||||
if typeID == C.CFStringGetTypeID() {
|
||||
return CFStringToString(C.CFStringRef(ref)), nil
|
||||
} else if typeID == C.CFDictionaryGetTypeID() {
|
||||
return ConvertCFDictionary(C.CFDictionaryRef(ref))
|
||||
} else if typeID == C.CFArrayGetTypeID() {
|
||||
arr := CFArrayToArray(C.CFArrayRef(ref))
|
||||
results := make([]interface{}, 0, len(arr))
|
||||
for _, ref := range arr {
|
||||
v, err := Convert(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, v)
|
||||
}
|
||||
return results, nil
|
||||
} else if typeID == C.CFDataGetTypeID() {
|
||||
b, err := CFDataToBytes(C.CFDataRef(ref))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
} else if typeID == C.CFNumberGetTypeID() {
|
||||
return CFNumberToInterface(C.CFNumberRef(ref)), nil
|
||||
} else if typeID == C.CFBooleanGetTypeID() {
|
||||
if C.CFBooleanGetValue(C.CFBooleanRef(ref)) != 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Invalid type: %s", CFTypeDescription(ref))
|
||||
}
|
||||
|
||||
// ConvertCFDictionary converts a CFDictionary to map (deep).
|
||||
func ConvertCFDictionary(d C.CFDictionaryRef) (map[interface{}]interface{}, error) {
|
||||
m := CFDictionaryToMap(d)
|
||||
result := make(map[interface{}]interface{})
|
||||
|
||||
for k, v := range m {
|
||||
gk, err := Convert(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gv, err := Convert(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[gk] = gv
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CFNumberToInterface converts the CFNumberRef to the most appropriate numeric
|
||||
// type.
|
||||
// This code is from github.com/kballard/go-osx-plist.
|
||||
func CFNumberToInterface(cfNumber C.CFNumberRef) interface{} {
|
||||
typ := C.CFNumberGetType(cfNumber)
|
||||
switch typ {
|
||||
case C.kCFNumberSInt8Type:
|
||||
var sint C.SInt8
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) //nolint
|
||||
return int8(sint)
|
||||
case C.kCFNumberSInt16Type:
|
||||
var sint C.SInt16
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) //nolint
|
||||
return int16(sint)
|
||||
case C.kCFNumberSInt32Type:
|
||||
var sint C.SInt32
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) //nolint
|
||||
return int32(sint)
|
||||
case C.kCFNumberSInt64Type:
|
||||
var sint C.SInt64
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) //nolint
|
||||
return int64(sint)
|
||||
case C.kCFNumberFloat32Type:
|
||||
var float C.Float32
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) //nolint
|
||||
return float32(float)
|
||||
case C.kCFNumberFloat64Type:
|
||||
var float C.Float64
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) //nolint
|
||||
return float64(float)
|
||||
case C.kCFNumberCharType:
|
||||
var char C.char
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&char)) //nolint
|
||||
return byte(char)
|
||||
case C.kCFNumberShortType:
|
||||
var short C.short
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&short)) //nolint
|
||||
return int16(short)
|
||||
case C.kCFNumberIntType:
|
||||
var i C.int
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&i)) //nolint
|
||||
return int32(i)
|
||||
case C.kCFNumberLongType:
|
||||
var long C.long
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&long)) //nolint
|
||||
return int(long)
|
||||
case C.kCFNumberLongLongType:
|
||||
// This is the only type that may actually overflow us
|
||||
var longlong C.longlong
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&longlong)) //nolint
|
||||
return int64(longlong)
|
||||
case C.kCFNumberFloatType:
|
||||
var float C.float
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) //nolint
|
||||
return float32(float)
|
||||
case C.kCFNumberDoubleType:
|
||||
var double C.double
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&double)) //nolint
|
||||
return float64(double)
|
||||
case C.kCFNumberCFIndexType:
|
||||
// CFIndex is a long
|
||||
var index C.CFIndex
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&index)) //nolint
|
||||
return int(index)
|
||||
case C.kCFNumberNSIntegerType:
|
||||
// We don't have a definition of NSInteger, but we know it's either an int or a long
|
||||
var nsInt C.long
|
||||
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&nsInt)) //nolint
|
||||
return int(nsInt)
|
||||
}
|
||||
panic("Unknown CFNumber type")
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
//go:build darwin || ios
|
||||
// +build darwin ios
|
||||
|
||||
package keychain
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -framework CoreFoundation
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
const nsPerSec = 1000 * 1000 * 1000
|
||||
|
||||
// absoluteTimeIntervalSince1970() returns the number of seconds from
|
||||
// the Unix epoch (1970-01-01T00:00:00+00:00) to the Core Foundation
|
||||
// absolute reference date (2001-01-01T00:00:00+00:00). It should be
|
||||
// exactly 978307200.
|
||||
func absoluteTimeIntervalSince1970() int64 {
|
||||
return int64(C.kCFAbsoluteTimeIntervalSince1970)
|
||||
}
|
||||
|
||||
func unixToAbsoluteTime(s int64, ns int64) C.CFAbsoluteTime {
|
||||
// Subtract as int64s first before converting to floating
|
||||
// point to minimize precision loss (assuming the given time
|
||||
// isn't much earlier than the Core Foundation absolute
|
||||
// reference date).
|
||||
abs := s - absoluteTimeIntervalSince1970()
|
||||
return C.CFAbsoluteTime(abs) + C.CFTimeInterval(ns)/nsPerSec
|
||||
}
|
||||
|
||||
func absoluteTimeToUnix(abs C.CFAbsoluteTime) (int64, int64) {
|
||||
i, frac := math.Modf(float64(abs))
|
||||
return int64(i) + absoluteTimeIntervalSince1970(), int64(frac * nsPerSec)
|
||||
}
|
||||
|
||||
// TimeToCFDate will convert the given time.Time to a CFDateRef, which
|
||||
// must be released with Release(ref).
|
||||
func TimeToCFDate(t time.Time) C.CFDateRef {
|
||||
s := t.Unix()
|
||||
ns := int64(t.Nanosecond())
|
||||
abs := unixToAbsoluteTime(s, ns)
|
||||
return C.CFDateCreate(C.kCFAllocatorDefault, abs)
|
||||
}
|
||||
|
||||
// CFDateToTime will convert the given CFDateRef to a time.Time.
|
||||
func CFDateToTime(d C.CFDateRef) time.Time {
|
||||
abs := C.CFDateGetAbsoluteTime(d)
|
||||
s, ns := absoluteTimeToUnix(abs)
|
||||
return time.Unix(s, ns)
|
||||
}
|
||||
|
||||
// Wrappers around C functions for testing.
|
||||
|
||||
func cfDateToAbsoluteTime(d C.CFDateRef) C.CFAbsoluteTime {
|
||||
return C.CFDateGetAbsoluteTime(d)
|
||||
}
|
||||
|
||||
func absoluteTimeToCFDate(abs C.CFAbsoluteTime) C.CFDateRef {
|
||||
return C.CFDateCreate(C.kCFAllocatorDefault, abs)
|
||||
}
|
||||
|
||||
func releaseCFDate(d C.CFDateRef) {
|
||||
Release(C.CFTypeRef(d))
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
//go:build darwin && ios
|
||||
// +build darwin,ios
|
||||
|
||||
package keychain
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -framework CoreFoundation -framework Security
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Security/Security.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
var AccessibleKey = attrKey(C.CFTypeRef(C.kSecAttrAccessible))
|
||||
var accessibleTypeRef = map[Accessible]C.CFTypeRef{
|
||||
AccessibleWhenUnlocked: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlocked),
|
||||
AccessibleAfterFirstUnlock: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlock),
|
||||
AccessibleAlways: C.CFTypeRef(C.kSecAttrAccessibleAlways),
|
||||
AccessibleWhenPasscodeSetThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly),
|
||||
AccessibleWhenUnlockedThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlockedThisDeviceOnly),
|
||||
AccessibleAfterFirstUnlockThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly),
|
||||
AccessibleAccessibleAlwaysThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAlwaysThisDeviceOnly),
|
||||
}
|
||||
+653
@@ -0,0 +1,653 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package keychain
|
||||
|
||||
// See https://developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/index.html for the APIs used below.
|
||||
|
||||
// Also see https://developer.apple.com/library/ios/documentation/Security/Conceptual/keychainServConcepts/01introduction/introduction.html .
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -framework CoreFoundation -framework Security
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Security/Security.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Error defines keychain errors
|
||||
type Error int
|
||||
|
||||
var (
|
||||
// ErrorUnimplemented corresponds to errSecUnimplemented result code
|
||||
ErrorUnimplemented = Error(C.errSecUnimplemented)
|
||||
// ErrorParam corresponds to errSecParam result code
|
||||
ErrorParam = Error(C.errSecParam)
|
||||
// ErrorAllocate corresponds to errSecAllocate result code
|
||||
ErrorAllocate = Error(C.errSecAllocate)
|
||||
// ErrorNotAvailable corresponds to errSecNotAvailable result code
|
||||
ErrorNotAvailable = Error(C.errSecNotAvailable)
|
||||
// ErrorAuthFailed corresponds to errSecAuthFailed result code
|
||||
ErrorAuthFailed = Error(C.errSecAuthFailed)
|
||||
// ErrorDuplicateItem corresponds to errSecDuplicateItem result code
|
||||
ErrorDuplicateItem = Error(C.errSecDuplicateItem)
|
||||
// ErrorItemNotFound corresponds to errSecItemNotFound result code
|
||||
ErrorItemNotFound = Error(C.errSecItemNotFound)
|
||||
// ErrorInteractionNotAllowed corresponds to errSecInteractionNotAllowed result code
|
||||
ErrorInteractionNotAllowed = Error(C.errSecInteractionNotAllowed)
|
||||
// ErrorDecode corresponds to errSecDecode result code
|
||||
ErrorDecode = Error(C.errSecDecode)
|
||||
// ErrorNoSuchKeychain corresponds to errSecNoSuchKeychain result code
|
||||
ErrorNoSuchKeychain = Error(C.errSecNoSuchKeychain)
|
||||
// ErrorNoAccessForItem corresponds to errSecNoAccessForItem result code
|
||||
ErrorNoAccessForItem = Error(C.errSecNoAccessForItem)
|
||||
// ErrorReadOnly corresponds to errSecReadOnly result code
|
||||
ErrorReadOnly = Error(C.errSecReadOnly)
|
||||
// ErrorInvalidKeychain corresponds to errSecInvalidKeychain result code
|
||||
ErrorInvalidKeychain = Error(C.errSecInvalidKeychain)
|
||||
// ErrorDuplicateKeyChain corresponds to errSecDuplicateKeychain result code
|
||||
ErrorDuplicateKeyChain = Error(C.errSecDuplicateKeychain)
|
||||
// ErrorWrongVersion corresponds to errSecWrongSecVersion result code
|
||||
ErrorWrongVersion = Error(C.errSecWrongSecVersion)
|
||||
// ErrorReadonlyAttribute corresponds to errSecReadOnlyAttr result code
|
||||
ErrorReadonlyAttribute = Error(C.errSecReadOnlyAttr)
|
||||
// ErrorInvalidSearchRef corresponds to errSecInvalidSearchRef result code
|
||||
ErrorInvalidSearchRef = Error(C.errSecInvalidSearchRef)
|
||||
// ErrorInvalidItemRef corresponds to errSecInvalidItemRef result code
|
||||
ErrorInvalidItemRef = Error(C.errSecInvalidItemRef)
|
||||
// ErrorDataNotAvailable corresponds to errSecDataNotAvailable result code
|
||||
ErrorDataNotAvailable = Error(C.errSecDataNotAvailable)
|
||||
// ErrorDataNotModifiable corresponds to errSecDataNotModifiable result code
|
||||
ErrorDataNotModifiable = Error(C.errSecDataNotModifiable)
|
||||
// ErrorInvalidOwnerEdit corresponds to errSecInvalidOwnerEdit result code
|
||||
ErrorInvalidOwnerEdit = Error(C.errSecInvalidOwnerEdit)
|
||||
// ErrorUserCanceled corresponds to errSecUserCanceled result code
|
||||
ErrorUserCanceled = Error(C.errSecUserCanceled)
|
||||
)
|
||||
|
||||
func checkError(errCode C.OSStatus) error {
|
||||
if errCode == C.errSecSuccess {
|
||||
return nil
|
||||
}
|
||||
return Error(errCode)
|
||||
}
|
||||
|
||||
func (k Error) Error() (msg string) {
|
||||
// SecCopyErrorMessageString is only available on OSX, so derive manually.
|
||||
// Messages derived from `$ security error $errcode`.
|
||||
switch k {
|
||||
case ErrorUnimplemented:
|
||||
msg = "Function or operation not implemented."
|
||||
case ErrorParam:
|
||||
msg = "One or more parameters passed to the function were not valid."
|
||||
case ErrorAllocate:
|
||||
msg = "Failed to allocate memory."
|
||||
case ErrorNotAvailable:
|
||||
msg = "No keychain is available. You may need to restart your computer."
|
||||
case ErrorAuthFailed:
|
||||
msg = "The user name or passphrase you entered is not correct."
|
||||
case ErrorDuplicateItem:
|
||||
msg = "The specified item already exists in the keychain."
|
||||
case ErrorItemNotFound:
|
||||
msg = "The specified item could not be found in the keychain."
|
||||
case ErrorInteractionNotAllowed:
|
||||
msg = "User interaction is not allowed."
|
||||
case ErrorDecode:
|
||||
msg = "Unable to decode the provided data."
|
||||
case ErrorNoSuchKeychain:
|
||||
msg = "The specified keychain could not be found."
|
||||
case ErrorNoAccessForItem:
|
||||
msg = "The specified item has no access control."
|
||||
case ErrorReadOnly:
|
||||
msg = "Read-only error."
|
||||
case ErrorReadonlyAttribute:
|
||||
msg = "The attribute is read-only."
|
||||
case ErrorInvalidKeychain:
|
||||
msg = "The keychain is not valid."
|
||||
case ErrorDuplicateKeyChain:
|
||||
msg = "A keychain with the same name already exists."
|
||||
case ErrorWrongVersion:
|
||||
msg = "The version is incorrect."
|
||||
case ErrorInvalidItemRef:
|
||||
msg = "The item reference is invalid."
|
||||
case ErrorInvalidSearchRef:
|
||||
msg = "The search reference is invalid."
|
||||
case ErrorDataNotAvailable:
|
||||
msg = "The data is not available."
|
||||
case ErrorDataNotModifiable:
|
||||
msg = "The data is not modifiable."
|
||||
case ErrorInvalidOwnerEdit:
|
||||
msg = "An invalid attempt to change the owner of an item."
|
||||
case ErrorUserCanceled:
|
||||
msg = "User canceled the operation."
|
||||
default:
|
||||
msg = "Keychain Error."
|
||||
}
|
||||
return fmt.Sprintf("%s (%d)", msg, k)
|
||||
}
|
||||
|
||||
// SecClass is the items class code
|
||||
type SecClass int
|
||||
|
||||
// Keychain Item Classes
|
||||
var (
|
||||
/*
|
||||
kSecClassGenericPassword item attributes:
|
||||
kSecAttrAccess (OS X only)
|
||||
kSecAttrAccessGroup (iOS; also OS X if kSecAttrSynchronizable specified)
|
||||
kSecAttrAccessible (iOS; also OS X if kSecAttrSynchronizable specified)
|
||||
kSecAttrAccount
|
||||
kSecAttrService
|
||||
*/
|
||||
SecClassGenericPassword SecClass = 1
|
||||
SecClassInternetPassword SecClass = 2
|
||||
)
|
||||
|
||||
// SecClassKey is the key type for SecClass
|
||||
var SecClassKey = attrKey(C.CFTypeRef(C.kSecClass))
|
||||
var secClassTypeRef = map[SecClass]C.CFTypeRef{
|
||||
SecClassGenericPassword: C.CFTypeRef(C.kSecClassGenericPassword),
|
||||
SecClassInternetPassword: C.CFTypeRef(C.kSecClassInternetPassword),
|
||||
}
|
||||
|
||||
var (
|
||||
// ServiceKey is for kSecAttrService
|
||||
ServiceKey = attrKey(C.CFTypeRef(C.kSecAttrService))
|
||||
|
||||
// ServerKey is for kSecAttrServer
|
||||
ServerKey = attrKey(C.CFTypeRef(C.kSecAttrServer))
|
||||
// ProtocolKey is for kSecAttrProtocol
|
||||
ProtocolKey = attrKey(C.CFTypeRef(C.kSecAttrProtocol))
|
||||
// AuthenticationTypeKey is for kSecAttrAuthenticationType
|
||||
AuthenticationTypeKey = attrKey(C.CFTypeRef(C.kSecAttrAuthenticationType))
|
||||
// PortKey is for kSecAttrPort
|
||||
PortKey = attrKey(C.CFTypeRef(C.kSecAttrPort))
|
||||
// PathKey is for kSecAttrPath
|
||||
PathKey = attrKey(C.CFTypeRef(C.kSecAttrPath))
|
||||
|
||||
// LabelKey is for kSecAttrLabel
|
||||
LabelKey = attrKey(C.CFTypeRef(C.kSecAttrLabel))
|
||||
// AccountKey is for kSecAttrAccount
|
||||
AccountKey = attrKey(C.CFTypeRef(C.kSecAttrAccount))
|
||||
// AccessGroupKey is for kSecAttrAccessGroup
|
||||
AccessGroupKey = attrKey(C.CFTypeRef(C.kSecAttrAccessGroup))
|
||||
// DataKey is for kSecValueData
|
||||
DataKey = attrKey(C.CFTypeRef(C.kSecValueData))
|
||||
// DescriptionKey is for kSecAttrDescription
|
||||
DescriptionKey = attrKey(C.CFTypeRef(C.kSecAttrDescription))
|
||||
// CommentKey is for kSecAttrComment
|
||||
CommentKey = attrKey(C.CFTypeRef(C.kSecAttrComment))
|
||||
// CreationDateKey is for kSecAttrCreationDate
|
||||
CreationDateKey = attrKey(C.CFTypeRef(C.kSecAttrCreationDate))
|
||||
// ModificationDateKey is for kSecAttrModificationDate
|
||||
ModificationDateKey = attrKey(C.CFTypeRef(C.kSecAttrModificationDate))
|
||||
)
|
||||
|
||||
// Synchronizable is the items synchronizable status
|
||||
type Synchronizable int
|
||||
|
||||
const (
|
||||
// SynchronizableDefault is the default setting
|
||||
SynchronizableDefault Synchronizable = 0
|
||||
// SynchronizableAny is for kSecAttrSynchronizableAny
|
||||
SynchronizableAny = 1
|
||||
// SynchronizableYes enables synchronization
|
||||
SynchronizableYes = 2
|
||||
// SynchronizableNo disables synchronization
|
||||
SynchronizableNo = 3
|
||||
)
|
||||
|
||||
// SynchronizableKey is the key type for Synchronizable
|
||||
var SynchronizableKey = attrKey(C.CFTypeRef(C.kSecAttrSynchronizable))
|
||||
var syncTypeRef = map[Synchronizable]C.CFTypeRef{
|
||||
SynchronizableAny: C.CFTypeRef(C.kSecAttrSynchronizableAny),
|
||||
SynchronizableYes: C.CFTypeRef(C.kCFBooleanTrue),
|
||||
SynchronizableNo: C.CFTypeRef(C.kCFBooleanFalse),
|
||||
}
|
||||
|
||||
// Accessible is the items accessibility
|
||||
type Accessible int
|
||||
|
||||
const (
|
||||
// AccessibleDefault is the default
|
||||
AccessibleDefault Accessible = 0
|
||||
// AccessibleWhenUnlocked is when unlocked
|
||||
AccessibleWhenUnlocked = 1
|
||||
// AccessibleAfterFirstUnlock is after first unlock
|
||||
AccessibleAfterFirstUnlock = 2
|
||||
// AccessibleAlways is always
|
||||
AccessibleAlways = 3
|
||||
// AccessibleWhenPasscodeSetThisDeviceOnly is when passcode is set
|
||||
AccessibleWhenPasscodeSetThisDeviceOnly = 4
|
||||
// AccessibleWhenUnlockedThisDeviceOnly is when unlocked for this device only
|
||||
AccessibleWhenUnlockedThisDeviceOnly = 5
|
||||
// AccessibleAfterFirstUnlockThisDeviceOnly is after first unlock for this device only
|
||||
AccessibleAfterFirstUnlockThisDeviceOnly = 6
|
||||
// AccessibleAccessibleAlwaysThisDeviceOnly is always for this device only
|
||||
AccessibleAccessibleAlwaysThisDeviceOnly = 7
|
||||
)
|
||||
|
||||
// MatchLimit is whether to limit results on query
|
||||
type MatchLimit int
|
||||
|
||||
const (
|
||||
// MatchLimitDefault is the default
|
||||
MatchLimitDefault MatchLimit = 0
|
||||
// MatchLimitOne limits to one result
|
||||
MatchLimitOne = 1
|
||||
// MatchLimitAll is no limit
|
||||
MatchLimitAll = 2
|
||||
)
|
||||
|
||||
// MatchLimitKey is key type for MatchLimit
|
||||
var MatchLimitKey = attrKey(C.CFTypeRef(C.kSecMatchLimit))
|
||||
var matchTypeRef = map[MatchLimit]C.CFTypeRef{
|
||||
MatchLimitOne: C.CFTypeRef(C.kSecMatchLimitOne),
|
||||
MatchLimitAll: C.CFTypeRef(C.kSecMatchLimitAll),
|
||||
}
|
||||
|
||||
// ReturnAttributesKey is key type for kSecReturnAttributes
|
||||
var ReturnAttributesKey = attrKey(C.CFTypeRef(C.kSecReturnAttributes))
|
||||
|
||||
// ReturnDataKey is key type for kSecReturnData
|
||||
var ReturnDataKey = attrKey(C.CFTypeRef(C.kSecReturnData))
|
||||
|
||||
// ReturnRefKey is key type for kSecReturnRef
|
||||
var ReturnRefKey = attrKey(C.CFTypeRef(C.kSecReturnRef))
|
||||
|
||||
// Item for adding, querying or deleting.
|
||||
type Item struct {
|
||||
// Values can be string, []byte, Convertable or CFTypeRef (constant).
|
||||
attr map[string]interface{}
|
||||
}
|
||||
|
||||
// SetSecClass sets the security class
|
||||
func (k *Item) SetSecClass(sc SecClass) {
|
||||
k.attr[SecClassKey] = secClassTypeRef[sc]
|
||||
}
|
||||
|
||||
// SetInt32 sets an int32 attribute for a string key
|
||||
func (k *Item) SetInt32(key string, v int32) {
|
||||
if v != 0 {
|
||||
k.attr[key] = v
|
||||
} else {
|
||||
delete(k.attr, key)
|
||||
}
|
||||
}
|
||||
|
||||
// SetString sets a string attibute for a string key
|
||||
func (k *Item) SetString(key string, s string) {
|
||||
if s != "" {
|
||||
k.attr[key] = s
|
||||
} else {
|
||||
delete(k.attr, key)
|
||||
}
|
||||
}
|
||||
|
||||
// SetService sets the service attribute (for generic application items)
|
||||
func (k *Item) SetService(s string) {
|
||||
k.SetString(ServiceKey, s)
|
||||
}
|
||||
|
||||
// SetServer sets the server attribute (for internet password items)
|
||||
func (k *Item) SetServer(s string) {
|
||||
k.SetString(ServerKey, s)
|
||||
}
|
||||
|
||||
// SetProtocol sets the protocol attribute (for internet password items)
|
||||
// Example values are: "htps", "http", "smb "
|
||||
func (k *Item) SetProtocol(s string) {
|
||||
k.SetString(ProtocolKey, s)
|
||||
}
|
||||
|
||||
// SetAuthenticationType sets the authentication type attribute (for internet password items)
|
||||
func (k *Item) SetAuthenticationType(s string) {
|
||||
k.SetString(AuthenticationTypeKey, s)
|
||||
}
|
||||
|
||||
// SetPort sets the port attribute (for internet password items)
|
||||
func (k *Item) SetPort(v int32) {
|
||||
k.SetInt32(PortKey, v)
|
||||
}
|
||||
|
||||
// SetPath sets the path attribute (for internet password items)
|
||||
func (k *Item) SetPath(s string) {
|
||||
k.SetString(PathKey, s)
|
||||
}
|
||||
|
||||
// SetAccount sets the account attribute
|
||||
func (k *Item) SetAccount(a string) {
|
||||
k.SetString(AccountKey, a)
|
||||
}
|
||||
|
||||
// SetLabel sets the label attribute
|
||||
func (k *Item) SetLabel(l string) {
|
||||
k.SetString(LabelKey, l)
|
||||
}
|
||||
|
||||
// SetDescription sets the description attribute
|
||||
func (k *Item) SetDescription(s string) {
|
||||
k.SetString(DescriptionKey, s)
|
||||
}
|
||||
|
||||
// SetComment sets the comment attribute
|
||||
func (k *Item) SetComment(s string) {
|
||||
k.SetString(CommentKey, s)
|
||||
}
|
||||
|
||||
// SetData sets the data attribute
|
||||
func (k *Item) SetData(b []byte) {
|
||||
if b != nil {
|
||||
k.attr[DataKey] = b
|
||||
} else {
|
||||
delete(k.attr, DataKey)
|
||||
}
|
||||
}
|
||||
|
||||
// SetAccessGroup sets the access group attribute
|
||||
func (k *Item) SetAccessGroup(ag string) {
|
||||
k.SetString(AccessGroupKey, ag)
|
||||
}
|
||||
|
||||
// SetSynchronizable sets the synchronizable attribute
|
||||
func (k *Item) SetSynchronizable(sync Synchronizable) {
|
||||
if sync != SynchronizableDefault {
|
||||
k.attr[SynchronizableKey] = syncTypeRef[sync]
|
||||
} else {
|
||||
delete(k.attr, SynchronizableKey)
|
||||
}
|
||||
}
|
||||
|
||||
// SetAccessible sets the accessible attribute
|
||||
func (k *Item) SetAccessible(accessible Accessible) {
|
||||
if accessible != AccessibleDefault {
|
||||
k.attr[AccessibleKey] = accessibleTypeRef[accessible]
|
||||
} else {
|
||||
delete(k.attr, AccessibleKey)
|
||||
}
|
||||
}
|
||||
|
||||
// SetMatchLimit sets the match limit
|
||||
func (k *Item) SetMatchLimit(matchLimit MatchLimit) {
|
||||
if matchLimit != MatchLimitDefault {
|
||||
k.attr[MatchLimitKey] = matchTypeRef[matchLimit]
|
||||
} else {
|
||||
delete(k.attr, MatchLimitKey)
|
||||
}
|
||||
}
|
||||
|
||||
// SetReturnAttributes sets the return value type on query
|
||||
func (k *Item) SetReturnAttributes(b bool) {
|
||||
k.attr[ReturnAttributesKey] = b
|
||||
}
|
||||
|
||||
// SetReturnData enables returning data on query
|
||||
func (k *Item) SetReturnData(b bool) {
|
||||
k.attr[ReturnDataKey] = b
|
||||
}
|
||||
|
||||
// SetReturnRef enables returning references on query
|
||||
func (k *Item) SetReturnRef(b bool) {
|
||||
k.attr[ReturnRefKey] = b
|
||||
}
|
||||
|
||||
// NewItem is a new empty keychain item
|
||||
func NewItem() Item {
|
||||
return Item{make(map[string]interface{})}
|
||||
}
|
||||
|
||||
// NewGenericPassword creates a generic password item with the default keychain. This is a convenience method.
|
||||
func NewGenericPassword(service string, account string, label string, data []byte, accessGroup string) Item {
|
||||
item := NewItem()
|
||||
item.SetSecClass(SecClassGenericPassword)
|
||||
item.SetService(service)
|
||||
item.SetAccount(account)
|
||||
item.SetLabel(label)
|
||||
item.SetData(data)
|
||||
item.SetAccessGroup(accessGroup)
|
||||
return item
|
||||
}
|
||||
|
||||
// AddItem adds a Item to a Keychain
|
||||
func AddItem(item Item) error {
|
||||
cfDict, err := ConvertMapToCFDictionary(item.attr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer Release(C.CFTypeRef(cfDict))
|
||||
|
||||
errCode := C.SecItemAdd(cfDict, nil)
|
||||
err = checkError(errCode)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateItem updates the queryItem with the parameters from updateItem
|
||||
func UpdateItem(queryItem Item, updateItem Item) error {
|
||||
cfDict, err := ConvertMapToCFDictionary(queryItem.attr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer Release(C.CFTypeRef(cfDict))
|
||||
cfDictUpdate, err := ConvertMapToCFDictionary(updateItem.attr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer Release(C.CFTypeRef(cfDictUpdate))
|
||||
errCode := C.SecItemUpdate(cfDict, cfDictUpdate)
|
||||
err = checkError(errCode)
|
||||
return err
|
||||
}
|
||||
|
||||
// QueryResult stores all possible results from queries.
|
||||
// Not all fields are applicable all the time. Results depend on query.
|
||||
type QueryResult struct {
|
||||
// For generic application items
|
||||
Service string
|
||||
|
||||
// For internet password items
|
||||
Server string
|
||||
Protocol string
|
||||
AuthenticationType string
|
||||
Port int32
|
||||
Path string
|
||||
|
||||
Account string
|
||||
AccessGroup string
|
||||
Label string
|
||||
Description string
|
||||
Comment string
|
||||
Data []byte
|
||||
CreationDate time.Time
|
||||
ModificationDate time.Time
|
||||
}
|
||||
|
||||
// QueryItemRef returns query result as CFTypeRef. You must release it when you are done.
|
||||
func QueryItemRef(item Item) (C.CFTypeRef, error) {
|
||||
cfDict, err := ConvertMapToCFDictionary(item.attr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer Release(C.CFTypeRef(cfDict))
|
||||
|
||||
var resultsRef C.CFTypeRef
|
||||
errCode := C.SecItemCopyMatching(cfDict, &resultsRef) //nolint
|
||||
if Error(errCode) == ErrorItemNotFound {
|
||||
return 0, nil
|
||||
}
|
||||
err = checkError(errCode)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return resultsRef, nil
|
||||
}
|
||||
|
||||
// QueryItem returns a list of query results.
|
||||
func QueryItem(item Item) ([]QueryResult, error) {
|
||||
resultsRef, err := QueryItemRef(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resultsRef == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
defer Release(resultsRef)
|
||||
|
||||
results := make([]QueryResult, 0, 1)
|
||||
|
||||
typeID := C.CFGetTypeID(resultsRef)
|
||||
if typeID == C.CFArrayGetTypeID() {
|
||||
arr := CFArrayToArray(C.CFArrayRef(resultsRef))
|
||||
for _, ref := range arr {
|
||||
elementTypeID := C.CFGetTypeID(ref)
|
||||
if elementTypeID == C.CFDictionaryGetTypeID() {
|
||||
item, err := convertResult(C.CFDictionaryRef(ref))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, *item)
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid result type (If you SetReturnRef(true) you should use QueryItemRef directly)")
|
||||
}
|
||||
}
|
||||
} else if typeID == C.CFDictionaryGetTypeID() {
|
||||
item, err := convertResult(C.CFDictionaryRef(resultsRef))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, *item)
|
||||
} else if typeID == C.CFDataGetTypeID() {
|
||||
b, err := CFDataToBytes(C.CFDataRef(resultsRef))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item := QueryResult{Data: b}
|
||||
results = append(results, item)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Invalid result type: %s", CFTypeDescription(resultsRef))
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func attrKey(ref C.CFTypeRef) string {
|
||||
return CFStringToString(C.CFStringRef(ref))
|
||||
}
|
||||
|
||||
func convertResult(d C.CFDictionaryRef) (*QueryResult, error) {
|
||||
m := CFDictionaryToMap(d)
|
||||
result := QueryResult{}
|
||||
for k, v := range m {
|
||||
switch attrKey(k) {
|
||||
case ServiceKey:
|
||||
result.Service = CFStringToString(C.CFStringRef(v))
|
||||
case ServerKey:
|
||||
result.Server = CFStringToString(C.CFStringRef(v))
|
||||
case ProtocolKey:
|
||||
result.Protocol = CFStringToString(C.CFStringRef(v))
|
||||
case AuthenticationTypeKey:
|
||||
result.AuthenticationType = CFStringToString(C.CFStringRef(v))
|
||||
case PortKey:
|
||||
val := CFNumberToInterface(C.CFNumberRef(v))
|
||||
result.Port = val.(int32)
|
||||
case PathKey:
|
||||
result.Path = CFStringToString(C.CFStringRef(v))
|
||||
case AccountKey:
|
||||
result.Account = CFStringToString(C.CFStringRef(v))
|
||||
case AccessGroupKey:
|
||||
result.AccessGroup = CFStringToString(C.CFStringRef(v))
|
||||
case LabelKey:
|
||||
result.Label = CFStringToString(C.CFStringRef(v))
|
||||
case DescriptionKey:
|
||||
result.Description = CFStringToString(C.CFStringRef(v))
|
||||
case CommentKey:
|
||||
result.Comment = CFStringToString(C.CFStringRef(v))
|
||||
case DataKey:
|
||||
b, err := CFDataToBytes(C.CFDataRef(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Data = b
|
||||
case CreationDateKey:
|
||||
result.CreationDate = CFDateToTime(C.CFDateRef(v))
|
||||
case ModificationDateKey:
|
||||
result.ModificationDate = CFDateToTime(C.CFDateRef(v))
|
||||
// default:
|
||||
// fmt.Printf("Unhandled key in conversion: %v = %v\n", cfTypeValue(k), cfTypeValue(v))
|
||||
}
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// DeleteGenericPasswordItem removes a generic password item.
|
||||
func DeleteGenericPasswordItem(service string, account string) error {
|
||||
item := NewItem()
|
||||
item.SetSecClass(SecClassGenericPassword)
|
||||
item.SetService(service)
|
||||
item.SetAccount(account)
|
||||
return DeleteItem(item)
|
||||
}
|
||||
|
||||
// DeleteItem removes a Item
|
||||
func DeleteItem(item Item) error {
|
||||
cfDict, err := ConvertMapToCFDictionary(item.attr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer Release(C.CFTypeRef(cfDict))
|
||||
|
||||
errCode := C.SecItemDelete(cfDict)
|
||||
return checkError(errCode)
|
||||
}
|
||||
|
||||
// GetAccountsForService is deprecated
|
||||
func GetAccountsForService(service string) ([]string, error) {
|
||||
return GetGenericPasswordAccounts(service)
|
||||
}
|
||||
|
||||
// GetGenericPasswordAccounts returns generic password accounts for service. This is a convenience method.
|
||||
func GetGenericPasswordAccounts(service string) ([]string, error) {
|
||||
query := NewItem()
|
||||
query.SetSecClass(SecClassGenericPassword)
|
||||
query.SetService(service)
|
||||
query.SetMatchLimit(MatchLimitAll)
|
||||
query.SetReturnAttributes(true)
|
||||
results, err := QueryItem(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accounts := make([]string, 0, len(results))
|
||||
for _, r := range results {
|
||||
accounts = append(accounts, r.Account)
|
||||
}
|
||||
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
// GetGenericPassword returns password data for service and account. This is a convenience method.
|
||||
// If item is not found returns nil, nil.
|
||||
func GetGenericPassword(service string, account string, label string, accessGroup string) ([]byte, error) {
|
||||
query := NewItem()
|
||||
query.SetSecClass(SecClassGenericPassword)
|
||||
query.SetService(service)
|
||||
query.SetAccount(account)
|
||||
query.SetLabel(label)
|
||||
query.SetAccessGroup(accessGroup)
|
||||
query.SetMatchLimit(MatchLimitOne)
|
||||
query.SetReturnData(true)
|
||||
results, err := QueryItem(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(results) > 1 {
|
||||
return nil, fmt.Errorf("Too many results")
|
||||
}
|
||||
if len(results) == 1 {
|
||||
return results[0].Data, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
//go:build darwin && !ios
|
||||
// +build darwin,!ios
|
||||
|
||||
package keychain
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -framework CoreFoundation -framework Security
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Security/Security.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// AccessibleKey is key for kSecAttrAccessible
|
||||
var AccessibleKey = attrKey(C.CFTypeRef(C.kSecAttrAccessible))
|
||||
var accessibleTypeRef = map[Accessible]C.CFTypeRef{
|
||||
AccessibleWhenUnlocked: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlocked),
|
||||
AccessibleAfterFirstUnlock: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlock),
|
||||
AccessibleAlways: C.CFTypeRef(C.kSecAttrAccessibleAlways),
|
||||
AccessibleWhenUnlockedThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlockedThisDeviceOnly),
|
||||
AccessibleAfterFirstUnlockThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly),
|
||||
AccessibleAccessibleAlwaysThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAlwaysThisDeviceOnly),
|
||||
|
||||
// Only available in 10.10
|
||||
//AccessibleWhenPasscodeSetThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly),
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package keychain
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base32"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var randRead = rand.Read
|
||||
|
||||
// RandomID returns random ID (base32) string with prefix, using 256 bits as
|
||||
// recommended by tptacek: https://gist.github.com/tqbf/be58d2d39690c3b366ad
|
||||
func RandomID(prefix string) (string, error) {
|
||||
buf, err := RandBytes(32)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str := base32.StdEncoding.EncodeToString(buf)
|
||||
str = strings.ReplaceAll(str, "=", "")
|
||||
str = prefix + str
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// RandBytes returns random bytes of length
|
||||
func RandBytes(length int) ([]byte, error) {
|
||||
buf := make([]byte, length)
|
||||
if _, err := randRead(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
Vendored
+3
@@ -1,6 +1,9 @@
|
||||
# github.com/danieljoos/wincred v1.2.2
|
||||
## explicit; go 1.18
|
||||
github.com/danieljoos/wincred
|
||||
# github.com/keybase/go-keychain v0.0.0-20250124001843-7f41edfa9689
|
||||
## explicit; go 1.21
|
||||
github.com/keybase/go-keychain
|
||||
# golang.org/x/sys v0.20.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/sys/windows
|
||||
|
||||
Reference in New Issue
Block a user