diff options
Diffstat (limited to 'vendor/github.com/99designs')
30 files changed, 3628 insertions, 0 deletions
diff --git a/vendor/github.com/99designs/go-keychain/.gitignore b/vendor/github.com/99designs/go-keychain/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/vendor/github.com/99designs/go-keychain/.gitignore @@ -0,0 +1,24 @@ +# 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 diff --git a/vendor/github.com/99designs/go-keychain/.golangci.yml b/vendor/github.com/99designs/go-keychain/.golangci.yml new file mode 100644 index 0000000..23aaf43 --- /dev/null +++ b/vendor/github.com/99designs/go-keychain/.golangci.yml @@ -0,0 +1,11 @@ +linters-settings: + gocritic: + disabled-checks: + - ifElseChain + - elseif + +linters: + enable: + - gofmt + - gocritic + - unconvert diff --git a/vendor/github.com/99designs/go-keychain/.travis.yml b/vendor/github.com/99designs/go-keychain/.travis.yml new file mode 100644 index 0000000..2fba239 --- /dev/null +++ b/vendor/github.com/99designs/go-keychain/.travis.yml @@ -0,0 +1,20 @@ +language: go + +os: + - osx + - linux + +before_install: + - go get golang.org/x/lint/golint + +script: + - go vet ./... + - golint ./... + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.16.0 + - golangci-lint run + - go test -tags skipsecretserviceintegrationtests ./... + +go: + - 1.10.x + - 1.11.x + - 1.12.x diff --git a/vendor/github.com/99designs/go-keychain/LICENSE b/vendor/github.com/99designs/go-keychain/LICENSE new file mode 100644 index 0000000..2d54c65 --- /dev/null +++ b/vendor/github.com/99designs/go-keychain/LICENSE @@ -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. + diff --git a/vendor/github.com/99designs/go-keychain/README.md b/vendor/github.com/99designs/go-keychain/README.md new file mode 100644 index 0000000..4a9eeb2 --- /dev/null +++ b/vendor/github.com/99designs/go-keychain/README.md @@ -0,0 +1,159 @@ +# Go Keychain + +[](https://travis-ci.org/keybase/go-keychain) + +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 +} + +accounts, err := keychain.GetGenericPasswordAccounts("MyService") +// Should have 1 account == "gabriel" + +err := keychain.DeleteGenericPasswordItem("MyService", "gabriel") +if err == keychain.ErrorNotFound { + // Not found +} +``` + +### OS X + +Creating a new keychain and add an item to it: + +```go + +// Add a new key chain into ~/Application Support/Keychains, with the provided password +k, err := keychain.NewKeychain("mykeychain.keychain", "my keychain password") +if err != nil { + // Error creating +} + +// 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.UseKeychain(k) +err := keychain.AddItem(item) +if err != nil { + // Error creating +} +``` + +Using a Keychain at path: + +```go +k, err := keychain.NewWithPath("mykeychain.keychain") +``` + +Set a trusted applications for item (OS X only): + +```go +item := keychain.NewGenericPassword("MyService", "gabriel", "A label", []byte("toomanysecrets"), "A123456789.group.com.mycorp") +trustedApplications := []string{"/Applications/Mail.app"} +item.SetAccess(&keychain.Access{Label: "Mail", TrustedApplications: trustedApplications}) +err := keychain.AddItem(item) +``` + +## 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) +``` diff --git a/vendor/github.com/99designs/go-keychain/corefoundation.go b/vendor/github.com/99designs/go-keychain/corefoundation.go new file mode 100644 index 0000000..c45ee7a --- /dev/null +++ b/vendor/github.com/99designs/go-keychain/corefoundation.go @@ -0,0 +1,359 @@ +// +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 +} + +// 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 []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") +} diff --git a/vendor/github.com/99designs/go-keychain/datetime.go b/vendor/github.com/99designs/go-keychain/datetime.go new file mode 100644 index 0000000..8124a6e --- /dev/null +++ b/vendor/github.com/99designs/go-keychain/datetime.go @@ -0,0 +1,68 @@ +// +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) { + int, frac := math.Modf(float64(abs)) + return int64(int) + 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)) +} diff --git a/vendor/github.com/99designs/go-keychain/ios.go b/vendor/github.com/99designs/go-keychain/ios.go new file mode 100644 index 0000000..abbaf28 --- /dev/null +++ b/vendor/github.com/99designs/go-keychain/ios.go @@ -0,0 +1,22 @@ +// +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), +} diff --git a/vendor/github.com/99designs/go-keychain/keychain.go b/vendor/github.com/99designs/go-keychain/keychain.go new file mode 100644 index 0000000..f5d02ad --- /dev/null +++ b/vendor/github.com/99designs/go-keychain/keychain.go @@ -0,0 +1,531 @@ +// +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) + // ErrorNoAcccessForItem corresponds to errSecNoAccessForItem result code + ErrorNoAccessForItem = Error(C.errSecNoAccessForItem) +) + +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." + 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)) + // 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)) + // 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] +} + +// 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 +func (k *Item) SetService(s string) { + k.SetString(ServiceKey, 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) +} + +// 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 { + Service string + Account string + AccessGroup string + Label string + Description 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 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 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 +} diff --git a/vendor/github.com/99designs/go-keychain/macos.go b/vendor/github.com/99designs/go-keychain/macos.go new file mode 100644 index 0000000..4004349 --- /dev/null +++ b/vendor/github.com/99designs/go-keychain/macos.go @@ -0,0 +1,272 @@ +// +build darwin,!ios + +package keychain + +/* +#cgo LDFLAGS: -framework CoreFoundation -framework Security +#cgo CFLAGS: -w + +#include <CoreFoundation/CoreFoundation.h> +#include <Security/Security.h> +*/ +import "C" +import ( + "os" + "unsafe" +) + +// 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), +} + +var ( + // AccessKey is key for kSecAttrAccess + AccessKey = attrKey(C.CFTypeRef(C.kSecAttrAccess)) +) + +// createAccess creates a SecAccessRef as CFTypeRef. +// The returned SecAccessRef, if non-nil, must be released via CFRelease. +func createAccess(label string, trustedApplications []string) (C.CFTypeRef, error) { + var err error + var labelRef C.CFStringRef + if labelRef, err = StringToCFString(label); err != nil { + return 0, err + } + defer C.CFRelease(C.CFTypeRef(labelRef)) + + var trustedApplicationsArray C.CFArrayRef + if trustedApplications != nil { + if len(trustedApplications) > 0 { + // Always prepend with empty string which signifies that we + // include a NULL application, which means ourselves. + trustedApplications = append([]string{""}, trustedApplications...) + } + + var trustedApplicationsRefs []C.CFTypeRef + for _, trustedApplication := range trustedApplications { + trustedApplicationRef, createErr := createTrustedApplication(trustedApplication) + if createErr != nil { + return 0, createErr + } + defer C.CFRelease(trustedApplicationRef) + trustedApplicationsRefs = append(trustedApplicationsRefs, trustedApplicationRef) + } + + trustedApplicationsArray = ArrayToCFArray(trustedApplicationsRefs) + defer C.CFRelease(C.CFTypeRef(trustedApplicationsArray)) + } + + var access C.SecAccessRef + errCode := C.SecAccessCreate(labelRef, trustedApplicationsArray, &access) //nolint + err = checkError(errCode) + if err != nil { + return 0, err + } + + return C.CFTypeRef(access), nil +} + +// createTrustedApplication creates a SecTrustedApplicationRef as a CFTypeRef. +// The returned SecTrustedApplicationRef, if non-nil, must be released via CFRelease. +func createTrustedApplication(trustedApplication string) (C.CFTypeRef, error) { + var trustedApplicationCStr *C.char + if trustedApplication != "" { + trustedApplicationCStr = C.CString(trustedApplication) + defer C.free(unsafe.Pointer(trustedApplicationCStr)) + } + + var trustedApplicationRef C.SecTrustedApplicationRef + errCode := C.SecTrustedApplicationCreateFromPath(trustedApplicationCStr, &trustedApplicationRef) //nolint + err := checkError(errCode) + if err != nil { + return 0, err + } + + return C.CFTypeRef(trustedApplicationRef), nil +} + +// Access defines whats applications can use the keychain item +type Access struct { + Label string + TrustedApplications []string +} + +// Convert converts Access to CFTypeRef. +// The returned CFTypeRef, if non-nil, must be released via CFRelease. +func (a Access) Convert() (C.CFTypeRef, error) { + return createAccess(a.Label, a.TrustedApplications) +} + +// SetAccess sets Access on Item +func (k *Item) SetAccess(a *Access) { + if a != nil { + k.attr[AccessKey] = a + } else { + delete(k.attr, AccessKey) + } +} + +// DeleteItemRef deletes a keychain item reference. +func DeleteItemRef(ref C.CFTypeRef) error { + errCode := C.SecKeychainItemDelete(C.SecKeychainItemRef(ref)) + return checkError(errCode) +} + +var ( + // KeychainKey is key for kSecUseKeychain + KeychainKey = attrKey(C.CFTypeRef(C.kSecUseKeychain)) + // MatchSearchListKey is key for kSecMatchSearchList + MatchSearchListKey = attrKey(C.CFTypeRef(C.kSecMatchSearchList)) +) + +// Keychain represents the path to a specific OSX keychain +type Keychain struct { + path string +} + +// NewKeychain creates a new keychain file with a password +func NewKeychain(path string, password string) (Keychain, error) { + return newKeychain(path, password, false) +} + +// NewKeychainWithPrompt creates a new Keychain and prompts user for password +func NewKeychainWithPrompt(path string) (Keychain, error) { + return newKeychain(path, "", true) +} + +func newKeychain(path, password string, promptUser bool) (Keychain, error) { + pathRef := C.CString(path) + defer C.free(unsafe.Pointer(pathRef)) + + var errCode C.OSStatus + var kref C.SecKeychainRef + + if promptUser { + errCode = C.SecKeychainCreate(pathRef, C.UInt32(0), nil, C.Boolean(1), 0, &kref) //nolint + } else { + passwordRef := C.CString(password) + defer C.free(unsafe.Pointer(passwordRef)) + errCode = C.SecKeychainCreate(pathRef, C.UInt32(len(password)), unsafe.Pointer(passwordRef), C.Boolean(0), 0, &kref) //nolint + } + + if err := checkError(errCode); err != nil { + return Keychain{}, err + } + + // TODO: Without passing in kref I get 'One or more parameters passed to the function were not valid (-50)' + defer Release(C.CFTypeRef(kref)) + + return Keychain{ + path: path, + }, nil +} + +// NewWithPath to use an existing keychain +func NewWithPath(path string) Keychain { + return Keychain{ + path: path, + } +} + +// Status returns the status of the keychain +func (kc Keychain) Status() error { + // returns no error even if it doesn't exist + kref, err := openKeychainRef(kc.path) + if err != nil { + return err + } + defer C.CFRelease(C.CFTypeRef(kref)) + + var status C.SecKeychainStatus + return checkError(C.SecKeychainGetStatus(kref, &status)) +} + +// The returned SecKeychainRef, if non-nil, must be released via CFRelease. +func openKeychainRef(path string) (C.SecKeychainRef, error) { + pathName := C.CString(path) + defer C.free(unsafe.Pointer(pathName)) + + var kref C.SecKeychainRef + if err := checkError(C.SecKeychainOpen(pathName, &kref)); err != nil { //nolint + return 0, err + } + + return kref, nil +} + +// UnlockAtPath unlocks keychain at path +func UnlockAtPath(path string, password string) error { + kref, err := openKeychainRef(path) + defer Release(C.CFTypeRef(kref)) + if err != nil { + return err + } + passwordRef := C.CString(password) + defer C.free(unsafe.Pointer(passwordRef)) + return checkError(C.SecKeychainUnlock(kref, C.UInt32(len(password)), unsafe.Pointer(passwordRef), C.Boolean(1))) +} + +// LockAtPath locks keychain at path +func LockAtPath(path string) error { + kref, err := openKeychainRef(path) + defer Release(C.CFTypeRef(kref)) + if err != nil { + return err + } + return checkError(C.SecKeychainLock(kref)) +} + +// Delete the Keychain +func (kc *Keychain) Delete() error { + return os.Remove(kc.path) +} + +// Convert Keychain to CFTypeRef. +// The returned CFTypeRef, if non-nil, must be released via CFRelease. +func (kc Keychain) Convert() (C.CFTypeRef, error) { + keyRef, err := openKeychainRef(kc.path) + return C.CFTypeRef(keyRef), err +} + +type keychainArray []Keychain + +// Convert the keychainArray to a CFTypeRef. +// The returned CFTypeRef, if non-nil, must be released via CFRelease. +func (ka keychainArray) Convert() (C.CFTypeRef, error) { + var refs = make([]C.CFTypeRef, len(ka)) + var err error + + for idx, kc := range ka { + if refs[idx], err = kc.Convert(); err != nil { + // If we error trying to convert lets release any we converted before + for _, ref := range refs { + if ref != 0 { + Release(ref) + } + } + return 0, err + } + } + + return C.CFTypeRef(ArrayToCFArray(refs)), nil +} + +// SetMatchSearchList sets match type on keychains +func (k *Item) SetMatchSearchList(karr ...Keychain) { + k.attr[MatchSearchListKey] = keychainArray(karr) +} + +// UseKeychain tells item to use the specified Keychain +func (k *Item) UseKeychain(kc Keychain) { + k.attr[KeychainKey] = kc +} diff --git a/vendor/github.com/99designs/go-keychain/util.go b/vendor/github.com/99designs/go-keychain/util.go new file mode 100644 index 0000000..29cbfc6 --- /dev/null +++ b/vendor/github.com/99designs/go-keychain/util.go @@ -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.Replace(str, "=", "", -1) + 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 +} diff --git a/vendor/github.com/99designs/keyring/.gitattributes b/vendor/github.com/99designs/keyring/.gitattributes new file mode 100644 index 0000000..d207b18 --- /dev/null +++ b/vendor/github.com/99designs/keyring/.gitattributes @@ -0,0 +1 @@ +*.go text eol=lf diff --git a/vendor/github.com/99designs/keyring/.gitignore b/vendor/github.com/99designs/keyring/.gitignore new file mode 100644 index 0000000..8000dd9 --- /dev/null +++ b/vendor/github.com/99designs/keyring/.gitignore @@ -0,0 +1 @@ +.vagrant diff --git a/vendor/github.com/99designs/keyring/.golangci.yml b/vendor/github.com/99designs/keyring/.golangci.yml new file mode 100644 index 0000000..f83428e --- /dev/null +++ b/vendor/github.com/99designs/keyring/.golangci.yml @@ -0,0 +1,29 @@ +linters: + enable: + - bodyclose + - contextcheck + - depguard + - durationcheck + - dupl + - errchkjson + - errname + - exhaustive + - exportloopref + - gocritic + - gofmt + - goimports + - makezero + - misspell + - nakedret + - nilerr + - nilnil + - noctx + - prealloc + - revive + # - rowserrcheck + - thelper + - tparallel + - unconvert + - unparam + # - wastedassign + - whitespace diff --git a/vendor/github.com/99designs/keyring/LICENSE b/vendor/github.com/99designs/keyring/LICENSE new file mode 100644 index 0000000..0fe9e46 --- /dev/null +++ b/vendor/github.com/99designs/keyring/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 99designs + +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. + diff --git a/vendor/github.com/99designs/keyring/README.md b/vendor/github.com/99designs/keyring/README.md new file mode 100644 index 0000000..629a6aa --- /dev/null +++ b/vendor/github.com/99designs/keyring/README.md @@ -0,0 +1,67 @@ +Keyring +======= +[](https://github.com/99designs/keyring/actions) +[](https://godoc.org/github.com/99designs/keyring) + +Keyring provides a common interface to a range of secure credential storage services. Originally developed as part of [AWS Vault](https://github.com/99designs/aws-vault), a command line tool for securely managing AWS access from developer workstations. + +Currently Keyring supports the following backends + * [macOS Keychain](https://support.apple.com/en-au/guide/keychain-access/welcome/mac) + * [Windows Credential Manager](https://support.microsoft.com/en-au/help/4026814/windows-accessing-credential-manager) + * Secret Service ([Gnome Keyring](https://wiki.gnome.org/Projects/GnomeKeyring), [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5)) + * [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5) + * [Pass](https://www.passwordstore.org/) + * [Encrypted file (JWT)](https://datatracker.ietf.org/doc/html/rfc7519) + * [KeyCtl](https://linux.die.net/man/1/keyctl) + + +## Usage + +The short version of how to use keyring is shown below. + +```go +ring, _ := keyring.Open(keyring.Config{ + ServiceName: "example", +}) + +_ = ring.Set(keyring.Item{ + Key: "foo", + Data: []byte("secret-bar"), +}) + +i, _ := ring.Get("foo") + +fmt.Printf("%s", i.Data) +``` + +For more detail on the API please check [the keyring godocs](https://godoc.org/github.com/99designs/keyring) + + +## Testing + +[Vagrant](https://www.vagrantup.com/) is used to create linux and windows test environments. + +```bash +# Start vagrant +vagrant up + +# Run go tests on all platforms +./bin/go-test +``` + + +## Contributing + +Contributions to the keyring package are most welcome from engineers of all backgrounds and skill levels. In particular the addition of extra backends across popular operating systems would be appreciated. + +This project will adhere to the [Go Community Code of Conduct](https://golang.org/conduct) in the github provided discussion spaces, with the moderators being the 99designs engineering team. + +To make a contribution: + + * Fork the repository + * Make your changes on the fork + * Submit a pull request back to this repo with a clear description of the problem you're solving + * Ensure your PR passes all current (and new) tests + * Ideally verify that [aws-vault](https://github.com/99designs/aws-vault) works with your changes (optional) + +...and we'll do our best to get your work merged in diff --git a/vendor/github.com/99designs/keyring/Vagrantfile b/vendor/github.com/99designs/keyring/Vagrantfile new file mode 100644 index 0000000..7d30d2a --- /dev/null +++ b/vendor/github.com/99designs/keyring/Vagrantfile @@ -0,0 +1,85 @@ +Vagrant.configure("2") do |config| + + config.vm.define "linux" do |linux| + linux.vm.box = "generic/fedora32" + + linux.vm.provider "virtualbox" do |vb| + vb.gui = true + vb.memory = 2048 + vb.cpus = 2 + + # VBoxVGA flickers constantly, use vmsvga instead which doesn't have that problem + vb.customize ["modifyvm", :id, "--graphicscontroller", "vmsvga"] + end + + # mount the project into /keyring + linux.vm.synced_folder ".", "/keyring" + + # install gnome desktop and auto login + linux.vm.provision "shell", inline: "sudo dnf install -y --exclude='gnome-initial-setup' @gnome-desktop langpacks-en" + linux.vm.provision "shell", inline: <<-SHELL + sudo sed -i -e 's/\\[daemon\\]/\\[daemon\\]\\nAutomaticLoginEnable=True\\nAutomaticLogin=vagrant\\n/' \ + /etc/gdm/custom.conf + SHELL + linux.vm.provision "shell", inline: "sudo systemctl set-default graphical.target" + linux.vm.provision "shell", inline: "sudo systemctl isolate graphical.target" + + # set the root password - sometimes prompts show up in gnome needing to install software + linux.vm.provision "shell", inline: "echo 'vagrant' | sudo passwd root --stdin" + + # install gnome keyring + linux.vm.provision "shell", inline: "sudo dnf install -y gnome-keyring seahorse" + + # install kwallet + linux.vm.provision "shell", inline: "sudo dnf install -y kwalletmanager5" + + # install pass + linux.vm.provision "shell", inline: "sudo dnf install -y pass" + + # install golang + linux.vm.provision "shell", inline: "sudo dnf install -y go" + end + + + config.vm.define "windows" do |windows| + windows.vm.box = "StefanScherer/windows_10" + + windows.vm.provider "virtualbox" do |vb| + vb.gui = true + vb.memory = 2048 + vb.cpus = 2 + end + + # mount the project into c:\keyring + windows.vm.synced_folder ".", "/keyring" + + # install chocolately + windows.vm.provision "shell", privileged: true, inline: <<-SHELL + Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) + choco feature disable -n=showDownloadProgress + SHELL + + # install golang + windows.vm.provision "shell", privileged: true, inline: "choco install -y git golang" + end + + config.vm.post_up_message = <<-MESSAGE + There are 2 vagrant boxes: + - linux + - OS: Fedora 32 with Gnome Desktop + - The keyring directory is mounted at /keyring + - Get a shell with 'vagrant ssh linux' + - When running go test, you'll need to use the GUI to click "Continue" on the prompts + - After provisioning, adjusting the virtualbox GUI window size doesn't cause the resolution to update. A 'vagrant reload linux' solves the problem + - windows + - OS: Windows 10 + - The keyring directory is mounted at C:\keyring + - Get a shell by starting PowerShell in the GUI + - You can run commands remotely using 'vagrant winrm -e windows CMD'. You'll need the -e (elevated privileges) if you want to interact with wincred + + Automated scripts for running go test on vagrant boxes (run these locally): + - ./bin/go-test-linux - Run tests on Linux + - ./bin/go-test-windows - Run tests on Windows + - ./bin/go-test - Run all tests - locally, linux and windows + MESSAGE +end diff --git a/vendor/github.com/99designs/keyring/array.go b/vendor/github.com/99designs/keyring/array.go new file mode 100644 index 0000000..3179cb5 --- /dev/null +++ b/vendor/github.com/99designs/keyring/array.go @@ -0,0 +1,54 @@ +package keyring + +// ArrayKeyring is a mock/non-secure backend that meets the Keyring interface. +// It is intended to be used to aid unit testing of code that relies on the package. +// NOTE: Do not use in production code. +type ArrayKeyring struct { + items map[string]Item +} + +// NewArrayKeyring returns an ArrayKeyring, optionally constructed with an initial slice +// of items. +func NewArrayKeyring(initial []Item) *ArrayKeyring { + kr := &ArrayKeyring{} + for _, i := range initial { + _ = kr.Set(i) + } + return kr +} + +// Get returns an Item matching Key. +func (k *ArrayKeyring) Get(key string) (Item, error) { + if i, ok := k.items[key]; ok { + return i, nil + } + return Item{}, ErrKeyNotFound +} + +// Set will store an item on the mock Keyring. +func (k *ArrayKeyring) Set(i Item) error { + if k.items == nil { + k.items = map[string]Item{} + } + k.items[i.Key] = i + return nil +} + +// Remove will delete an Item from the Keyring. +func (k *ArrayKeyring) Remove(key string) error { + delete(k.items, key) + return nil +} + +// Keys provides a slice of all Item keys on the Keyring. +func (k *ArrayKeyring) Keys() ([]string, error) { + var keys = []string{} + for key := range k.items { + keys = append(keys, key) + } + return keys, nil +} + +func (k *ArrayKeyring) GetMetadata(_ string) (Metadata, error) { + return Metadata{}, ErrMetadataNeedsCredentials +} diff --git a/vendor/github.com/99designs/keyring/config.go b/vendor/github.com/99designs/keyring/config.go new file mode 100644 index 0000000..590af7c --- /dev/null +++ b/vendor/github.com/99designs/keyring/config.go @@ -0,0 +1,58 @@ +package keyring + +// Config contains configuration for keyring. +type Config struct { + // AllowedBackends is a whitelist of backend providers that can be used. Nil means all available. + AllowedBackends []BackendType + + // ServiceName is a generic service name that is used by backends that support the concept + ServiceName string + + // MacOSKeychainNameKeychainName is the name of the macOS keychain that is used + KeychainName string + + // KeychainTrustApplication is whether the calling application should be trusted by default by items + KeychainTrustApplication bool + + // KeychainSynchronizable is whether the item can be synchronized to iCloud + KeychainSynchronizable bool + + // KeychainAccessibleWhenUnlocked is whether the item is accessible when the device is locked + KeychainAccessibleWhenUnlocked bool + + // KeychainPasswordFunc is an optional function used to prompt the user for a password + KeychainPasswordFunc PromptFunc + + // FilePasswordFunc is a required function used to prompt the user for a password + FilePasswordFunc PromptFunc + + // FileDir is the directory that keyring files are stored in, ~/ is resolved to the users' home dir + FileDir string + + // KeyCtlScope is the scope of the kernel keyring (either "user", "session", "process" or "thread") + KeyCtlScope string + + // KeyCtlPerm is the permission mask to use for new keys + KeyCtlPerm uint32 + + // KWalletAppID is the application id for KWallet + KWalletAppID string + + // KWalletFolder is the folder for KWallet + KWalletFolder string + + // LibSecretCollectionName is the name collection in secret-service + LibSecretCollectionName string + + // PassDir is the pass password-store directory, ~/ is resolved to the users' home dir + PassDir string + + // PassCmd is the name of the pass executable + PassCmd string + + // PassPrefix is a string prefix to prepend to the item path stored in pass + PassPrefix string + + // WinCredPrefix is a string prefix to prepend to the key name + WinCredPrefix string +} diff --git a/vendor/github.com/99designs/keyring/docker-compose.yml b/vendor/github.com/99designs/keyring/docker-compose.yml new file mode 100644 index 0000000..9020220 --- /dev/null +++ b/vendor/github.com/99designs/keyring/docker-compose.yml @@ -0,0 +1,7 @@ +version: "3.9" +services: + keyring: + image: golang:1.19 + volumes: + - .:/usr/local/src/keyring + working_dir: /usr/local/src/keyring diff --git a/vendor/github.com/99designs/keyring/file.go b/vendor/github.com/99designs/keyring/file.go new file mode 100644 index 0000000..0d25b57 --- /dev/null +++ b/vendor/github.com/99designs/keyring/file.go @@ -0,0 +1,180 @@ +package keyring + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "time" + + jose "github.com/dvsekhvalnov/jose2go" + "github.com/mtibben/percent" +) + +func init() { + supportedBackends[FileBackend] = opener(func(cfg Config) (Keyring, error) { + return &fileKeyring{ + dir: cfg.FileDir, + passwordFunc: cfg.FilePasswordFunc, + }, nil + }) +} + +var filenameEscape = func(s string) string { + return percent.Encode(s, "/") +} +var filenameUnescape = percent.Decode + +type fileKeyring struct { + dir string + passwordFunc PromptFunc + password string +} + +func (k *fileKeyring) resolveDir() (string, error) { + if k.dir == "" { + return "", fmt.Errorf("No directory provided for file keyring") + } + + dir, err := ExpandTilde(k.dir) + if err != nil { + return "", err + } + + stat, err := os.Stat(dir) + if os.IsNotExist(err) { + err = os.MkdirAll(dir, 0700) + } else if err != nil && stat != nil && !stat.IsDir() { + err = fmt.Errorf("%s is a file, not a directory", dir) + } + + return dir, err +} + +func (k *fileKeyring) unlock() error { + dir, err := k.resolveDir() + if err != nil { + return err + } + + if k.password == "" { + pwd, err := k.passwordFunc(fmt.Sprintf("Enter passphrase to unlock %q", dir)) + if err != nil { + return err + } + k.password = pwd + } + + return nil +} + +func (k *fileKeyring) Get(key string) (Item, error) { + filename, err := k.filename(key) + if err != nil { + return Item{}, err + } + + bytes, err := os.ReadFile(filename) + if os.IsNotExist(err) { + return Item{}, ErrKeyNotFound + } else if err != nil { + return Item{}, err + } + + if err = k.unlock(); err != nil { + return Item{}, err + } + + payload, _, err := jose.Decode(string(bytes), k.password) + if err != nil { + return Item{}, err + } + + var decoded Item + err = json.Unmarshal([]byte(payload), &decoded) + + return decoded, err +} + +func (k *fileKeyring) GetMetadata(key string) (Metadata, error) { + filename, err := k.filename(key) + if err != nil { + return Metadata{}, err + } + + stat, err := os.Stat(filename) + if os.IsNotExist(err) { + return Metadata{}, ErrKeyNotFound + } else if err != nil { + return Metadata{}, err + } + + // For the File provider, all internal data is encrypted, not just the + // credentials. Thus we only have the timestamps. Return a nil *Item. + // + // If we want to change this ... how portable are extended file attributes + // these days? Would it break user expectations of the security model to + // leak data into those? I'm hesitant to do so. + + return Metadata{ + ModificationTime: stat.ModTime(), + }, nil +} + +func (k *fileKeyring) Set(i Item) error { + bytes, err := json.Marshal(i) + if err != nil { + return err + } + + if err = k.unlock(); err != nil { + return err + } + + token, err := jose.Encrypt(string(bytes), jose.PBES2_HS256_A128KW, jose.A256GCM, k.password, + jose.Headers(map[string]interface{}{ + "created": time.Now().String(), + })) + if err != nil { + return err + } + + filename, err := k.filename(i.Key) + if err != nil { + return err + } + return os.WriteFile(filename, []byte(token), 0600) +} + +func (k *fileKeyring) filename(key string) (string, error) { + dir, err := k.resolveDir() + if err != nil { + return "", err + } + + return filepath.Join(dir, filenameEscape(key)), nil +} + +func (k *fileKeyring) Remove(key string) error { + filename, err := k.filename(key) + if err != nil { + return err + } + + return os.Remove(filename) +} + +func (k *fileKeyring) Keys() ([]string, error) { + dir, err := k.resolveDir() + if err != nil { + return nil, err + } + + var keys = []string{} + files, _ := os.ReadDir(dir) + for _, f := range files { + keys = append(keys, filenameUnescape(f.Name())) + } + + return keys, nil +} diff --git a/vendor/github.com/99designs/keyring/keychain.go b/vendor/github.com/99designs/keyring/keychain.go new file mode 100644 index 0000000..d4e634e --- /dev/null +++ b/vendor/github.com/99designs/keyring/keychain.go @@ -0,0 +1,301 @@ +//go:build darwin && cgo +// +build darwin,cgo + +package keyring + +import ( + "errors" + "fmt" + + gokeychain "github.com/99designs/go-keychain" +) + +type keychain struct { + path string + service string + + passwordFunc PromptFunc + + isSynchronizable bool + isAccessibleWhenUnlocked bool + isTrusted bool +} + +func init() { + supportedBackends[KeychainBackend] = opener(func(cfg Config) (Keyring, error) { + kc := &keychain{ + service: cfg.ServiceName, + passwordFunc: cfg.KeychainPasswordFunc, + + // Set the isAccessibleWhenUnlocked to the boolean value of + // KeychainAccessibleWhenUnlocked is a shorthand for setting the accessibility value. + // See: https://developer.apple.com/documentation/security/ksecattraccessiblewhenunlocked + isAccessibleWhenUnlocked: cfg.KeychainAccessibleWhenUnlocked, + } + if cfg.KeychainName != "" { + kc.path = cfg.KeychainName + ".keychain" + } + if cfg.KeychainTrustApplication { + kc.isTrusted = true + } + return kc, nil + }) +} + +func (k *keychain) Get(key string) (Item, error) { + query := gokeychain.NewItem() + query.SetSecClass(gokeychain.SecClassGenericPassword) + query.SetService(k.service) + query.SetAccount(key) + query.SetMatchLimit(gokeychain.MatchLimitOne) + query.SetReturnAttributes(true) + query.SetReturnData(true) + + if k.path != "" { + // When we are querying, we don't create by default + query.SetMatchSearchList(gokeychain.NewWithPath(k.path)) + } + + debugf("Querying keychain for service=%q, account=%q, keychain=%q", k.service, key, k.path) + results, err := gokeychain.QueryItem(query) + if err == gokeychain.ErrorItemNotFound || len(results) == 0 { + debugf("No results found") + return Item{}, ErrKeyNotFound + } + + if err != nil { + debugf("Error: %#v", err) + return Item{}, err + } + + item := Item{ + Key: key, + Data: results[0].Data, + Label: results[0].Label, + Description: results[0].Description, + } + + debugf("Found item %q", results[0].Label) + return item, nil +} + +func (k *keychain) GetMetadata(key string) (Metadata, error) { + query := gokeychain.NewItem() + query.SetSecClass(gokeychain.SecClassGenericPassword) + query.SetService(k.service) + query.SetAccount(key) + query.SetMatchLimit(gokeychain.MatchLimitOne) + query.SetReturnAttributes(true) + query.SetReturnData(false) + query.SetReturnRef(true) + + debugf("Querying keychain for metadata of service=%q, account=%q, keychain=%q", k.service, key, k.path) + results, err := gokeychain.QueryItem(query) + if err == gokeychain.ErrorItemNotFound || len(results) == 0 { + debugf("No results found") + return Metadata{}, ErrKeyNotFound + } else if err != nil { + debugf("Error: %#v", err) + return Metadata{}, err + } + + md := Metadata{ + Item: &Item{ + Key: key, + Label: results[0].Label, + Description: results[0].Description, + }, + ModificationTime: results[0].ModificationDate, + } + + debugf("Found metadata for %q", md.Item.Label) + + return md, nil +} + +func (k *keychain) updateItem(kc gokeychain.Keychain, kcItem gokeychain.Item, account string) error { + queryItem := gokeychain.NewItem() + queryItem.SetSecClass(gokeychain.SecClassGenericPassword) + queryItem.SetService(k.service) + queryItem.SetAccount(account) + queryItem.SetMatchLimit(gokeychain.MatchLimitOne) + queryItem.SetReturnAttributes(true) + + if k.path != "" { + queryItem.SetMatchSearchList(kc) + } + + results, err := gokeychain.QueryItem(queryItem) + if err != nil { + return fmt.Errorf("Failed to query keychain: %v", err) + } + if len(results) == 0 { + return errors.New("no results") + } + + // Don't call SetAccess() as this will cause multiple prompts on update, even when we are not updating the AccessList + kcItem.SetAccess(nil) + + if err := gokeychain.UpdateItem(queryItem, kcItem); err != nil { + return fmt.Errorf("Failed to update item in keychain: %v", err) + } + + return nil +} + +func (k *keychain) Set(item Item) error { + var kc gokeychain.Keychain + + // when we are setting a value, we create or open + if k.path != "" { + var err error + kc, err = k.createOrOpen() + if err != nil { + return err + } + } + + kcItem := gokeychain.NewItem() + kcItem.SetSecClass(gokeychain.SecClassGenericPassword) + kcItem.SetService(k.service) + kcItem.SetAccount(item.Key) + kcItem.SetLabel(item.Label) + kcItem.SetDescription(item.Description) + kcItem.SetData(item.Data) + + if k.path != "" { + kcItem.UseKeychain(kc) + } + + if k.isSynchronizable && !item.KeychainNotSynchronizable { + kcItem.SetSynchronizable(gokeychain.SynchronizableYes) + } + + if k.isAccessibleWhenUnlocked { + kcItem.SetAccessible(gokeychain.AccessibleWhenUnlocked) + } + + isTrusted := k.isTrusted && !item.KeychainNotTrustApplication + + if isTrusted { + debugf("Keychain item trusts keyring") + kcItem.SetAccess(&gokeychain.Access{ + Label: item.Label, + TrustedApplications: nil, + }) + } else { + debugf("Keychain item doesn't trust keyring") + kcItem.SetAccess(&gokeychain.Access{ + Label: item.Label, + TrustedApplications: []string{}, + }) + } + + debugf("Adding service=%q, label=%q, account=%q, trusted=%v to osx keychain %q", k.service, item.Label, item.Key, isTrusted, k.path) + + err := gokeychain.AddItem(kcItem) + + if err == gokeychain.ErrorDuplicateItem { + debugf("Item already exists, updating") + err = k.updateItem(kc, kcItem, item.Key) + } + + if err != nil { + return err + } + + return nil +} + +func (k *keychain) Remove(key string) error { + item := gokeychain.NewItem() + item.SetSecClass(gokeychain.SecClassGenericPassword) + item.SetService(k.service) + item.SetAccount(key) + + if k.path != "" { + kc := gokeychain.NewWithPath(k.path) + + if err := kc.Status(); err != nil { + if err == gokeychain.ErrorNoSuchKeychain { + return ErrKeyNotFound + } + return err + } + + item.SetMatchSearchList(kc) + } + + debugf("Removing keychain item service=%q, account=%q, keychain %q", k.service, key, k.path) + err := gokeychain.DeleteItem(item) + if err == gokeychain.ErrorItemNotFound { + return ErrKeyNotFound + } + + return err +} + +func (k *keychain) Keys() ([]string, error) { + query := gokeychain.NewItem() + query.SetSecClass(gokeychain.SecClassGenericPassword) + query.SetService(k.service) + query.SetMatchLimit(gokeychain.MatchLimitAll) + query.SetReturnAttributes(true) + + if k.path != "" { + kc := gokeychain.NewWithPath(k.path) + + if err := kc.Status(); err != nil { + if err == gokeychain.ErrorNoSuchKeychain { + return []string{}, nil + } + return nil, err + } + + query.SetMatchSearchList(kc) + } + + debugf("Querying keychain for service=%q, keychain=%q", k.service, k.path) + results, err := gokeychain.QueryItem(query) + if err != nil { + return nil, err + } + + debugf("Found %d results", len(results)) + accountNames := make([]string, len(results)) + for idx, r := range results { + accountNames[idx] = r.Account + } + + return accountNames, nil +} + +func (k *keychain) createOrOpen() (gokeychain.Keychain, error) { + kc := gokeychain.NewWithPath(k.path) + + debugf("Checking keychain status") + err := kc.Status() + if err == nil { + debugf("Keychain status returned nil, keychain exists") + return kc, nil + } + + debugf("Keychain status returned error: %v", err) + + if err != gokeychain.ErrorNoSuchKeychain { + return gokeychain.Keychain{}, err + } + + if k.passwordFunc == nil { + debugf("Creating keychain %s with prompt", k.path) + return gokeychain.NewKeychainWithPrompt(k.path) + } + + passphrase, err := k.passwordFunc("Enter passphrase for keychain") + if err != nil { + return gokeychain.Keychain{}, err + } + + debugf("Creating keychain %s with provided password", k.path) + return gokeychain.NewKeychain(k.path, passphrase) +} diff --git a/vendor/github.com/99designs/keyring/keyctl.go b/vendor/github.com/99designs/keyring/keyctl.go new file mode 100644 index 0000000..bd8018a --- /dev/null +++ b/vendor/github.com/99designs/keyring/keyctl.go @@ -0,0 +1,327 @@ +//go:build linux +// +build linux + +package keyring + +import ( + "errors" + "fmt" + "strings" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +//nolint:revive +const ( + KEYCTL_PERM_VIEW = uint32(1 << 0) + KEYCTL_PERM_READ = uint32(1 << 1) + KEYCTL_PERM_WRITE = uint32(1 << 2) + KEYCTL_PERM_SEARCH = uint32(1 << 3) + KEYCTL_PERM_LINK = uint32(1 << 4) + KEYCTL_PERM_SETATTR = uint32(1 << 5) + KEYCTL_PERM_ALL = uint32((1 << 6) - 1) + + KEYCTL_PERM_OTHERS = 0 + KEYCTL_PERM_GROUP = 8 + KEYCTL_PERM_USER = 16 + KEYCTL_PERM_PROCESS = 24 +) + +// GetPermissions constructs the permission mask from the elements. +func GetPermissions(process, user, group, others uint32) uint32 { + perm := others << KEYCTL_PERM_OTHERS + perm |= group << KEYCTL_PERM_GROUP + perm |= user << KEYCTL_PERM_USER + perm |= process << KEYCTL_PERM_PROCESS + + return perm +} + +// GetKeyringIDForScope get the keyring ID for a given scope. +func GetKeyringIDForScope(scope string) (int32, error) { + ringRef, err := getKeyringForScope(scope) + if err != nil { + return 0, err + } + id, err := unix.KeyctlGetKeyringID(int(ringRef), false) + return int32(id), err +} + +type keyctlKeyring struct { + keyring int32 + perm uint32 +} + +func init() { + supportedBackends[KeyCtlBackend] = opener(func(cfg Config) (Keyring, error) { + keyring := keyctlKeyring{} + if cfg.KeyCtlPerm > 0 { + keyring.perm = cfg.KeyCtlPerm + } + + parent, err := getKeyringForScope(cfg.KeyCtlScope) + if err != nil { + return nil, fmt.Errorf("accessing %q keyring failed: %v", cfg.KeyCtlScope, err) + } + + // Check for named keyrings + keyring.keyring = parent + if cfg.ServiceName != "" { + namedKeyring, err := keyctlSearch(parent, "keyring", cfg.ServiceName) + if err != nil { + if !errors.Is(err, syscall.ENOKEY) { + return nil, fmt.Errorf("opening named %q keyring failed: %v", cfg.KeyCtlScope, err) + } + + // Keyring does not yet exist, create it + namedKeyring, err = keyring.createNamedKeyring(parent, cfg.ServiceName) + if err != nil { + return nil, fmt.Errorf("creating named %q keyring failed: %v", cfg.KeyCtlScope, err) + } + } + keyring.keyring = namedKeyring + } + + return &keyring, nil + }) +} + +func (k *keyctlKeyring) Get(name string) (Item, error) { + key, err := keyctlSearch(k.keyring, "user", name) + if err != nil { + if errors.Is(err, syscall.ENOKEY) { + return Item{}, ErrKeyNotFound + } + return Item{}, err + } + // data, err := key.Get() + data, err := keyctlRead(key) + if err != nil { + return Item{}, err + } + + item := Item{ + Key: name, + Data: data, + } + + return item, nil +} + +// GetMetadata for pass returns an error indicating that it's unsupported for this backend. +// TODO: We can deliver metadata different from the defined ones (e.g. permissions, expire-time, etc). +func (k *keyctlKeyring) GetMetadata(_ string) (Metadata, error) { + return Metadata{}, ErrMetadataNotSupported +} + +func (k *keyctlKeyring) Set(item Item) error { + if k.perm == 0 { + // Keep the default permissions (alswrv-----v------------) + _, err := keyctlAdd(k.keyring, "user", item.Key, item.Data) + return err + } + + // By default we loose possession of the key in anything above the session keyring. + // Together with the default permissions (which cannot be changed during creation) we + // cannot change the permissions without possessing the key. Therefore, create the + // key in the session keyring, change permissions and then link to the target + // keyring and unlink from the intermediate keyring again. + key, err := keyctlAdd(unix.KEY_SPEC_SESSION_KEYRING, "user", item.Key, item.Data) + if err != nil { + return fmt.Errorf("adding key to session failed: %v", err) + } + + if err := keyctlSetperm(key, k.perm); err != nil { + return fmt.Errorf("setting permission 0x%x failed: %v", k.perm, err) + } + + if err := keyctlLink(k.keyring, key); err != nil { + return fmt.Errorf("linking key to keyring failed: %v", err) + } + + if err := keyctlUnlink(unix.KEY_SPEC_SESSION_KEYRING, key); err != nil { + return fmt.Errorf("unlinking key from session failed: %v", err) + } + + return nil +} + +func (k *keyctlKeyring) Remove(name string) error { + key, err := keyctlSearch(k.keyring, "user", name) + if err != nil { + return ErrKeyNotFound + } + + return keyctlUnlink(k.keyring, key) +} + +func (k *keyctlKeyring) Keys() ([]string, error) { + results := []string{} + + data, err := keyctlRead(k.keyring) + if err != nil { + return nil, fmt.Errorf("reading keyring failed: %v", err) + } + ids, err := keyctlConvertKeyBuffer(data) + if err != nil { + return nil, fmt.Errorf("converting raw keylist failed: %v", err) + } + + for _, id := range ids { + info, err := keyctlDescribe(id) + if err != nil { + return nil, err + } + if info["type"] == "user" { + results = append(results, info["description"]) + } + } + + return results, nil +} + +func (k *keyctlKeyring) createNamedKeyring(parent int32, name string) (int32, error) { + if k.perm == 0 { + // Keep the default permissions (alswrv-----v------------) + return keyctlAdd(parent, "keyring", name, nil) + } + + // By default we loose possession of the keyring in anything above the session keyring. + // Together with the default permissions (which cannot be changed during creation) we + // cannot change the permissions without possessing the keyring. Therefore, create the + // keyring linked to the session keyring, change permissions and then link to the target + // keyring and unlink from the intermediate keyring again. + keyring, err := keyctlAdd(unix.KEY_SPEC_SESSION_KEYRING, "keyring", name, nil) + if err != nil { + return 0, fmt.Errorf("creating keyring failed: %v", err) + } + + if err := keyctlSetperm(keyring, k.perm); err != nil { + return 0, fmt.Errorf("setting permission 0x%x failed: %v", k.perm, err) + } + + if err := keyctlLink(k.keyring, keyring); err != nil { + return 0, fmt.Errorf("linking keyring failed: %v", err) + } + + if err := keyctlUnlink(unix.KEY_SPEC_SESSION_KEYRING, keyring); err != nil { + return 0, fmt.Errorf("unlinking keyring from session failed: %v", err) + } + + return keyring, nil +} + +func getKeyringForScope(scope string) (int32, error) { + switch scope { + case "user": + return int32(unix.KEY_SPEC_USER_KEYRING), nil + case "usersession": + return int32(unix.KEY_SPEC_USER_SESSION_KEYRING), nil + case "group": + // Not yet implemented in the kernel + // return int32(unix.KEY_SPEC_GROUP_KEYRING) + return 0, fmt.Errorf("scope %q not yet implemented", scope) + case "session": + return int32(unix.KEY_SPEC_SESSION_KEYRING), nil + case "process": + return int32(unix.KEY_SPEC_PROCESS_KEYRING), nil + case "thread": + return int32(unix.KEY_SPEC_THREAD_KEYRING), nil + } + return 0, fmt.Errorf("unknown scope %q", scope) +} + +func keyctlAdd(parent int32, keytype, key string, data []byte) (int32, error) { + id, err := unix.AddKey(keytype, key, data, int(parent)) + if err != nil { + return 0, err + } + return int32(id), nil +} + +func keyctlSearch(id int32, idtype, name string) (int32, error) { + key, err := unix.KeyctlSearch(int(id), idtype, name, 0) + if err != nil { + return 0, err + } + return int32(key), nil +} + +func keyctlRead(id int32) ([]byte, error) { + var buffer []byte + + for { + length, err := unix.KeyctlBuffer(unix.KEYCTL_READ, int(id), buffer, 0) + if err != nil { + return nil, err + } + + // Return the buffer if it was large enough + if length <= len(buffer) { + return buffer[:length], nil + } + + // Next try with a larger buffer + buffer = make([]byte, length) + } +} + +func keyctlDescribe(id int32) (map[string]string, error) { + description, err := unix.KeyctlString(unix.KEYCTL_DESCRIBE, int(id)) + if err != nil { + return nil, err + } + fields := strings.Split(description, ";") + if len(fields) < 1 { + return nil, fmt.Errorf("no data") + } + + data := make(map[string]string) + names := []string{"type", "uid", "gid", "perm"} // according to keyctlDescribe(3) new fields are added at the end + data["description"] = fields[len(fields)-1] // according to keyctlDescribe(3) description is always last + for i, f := range fields[:len(fields)-1] { + if i >= len(names) { + // Do not stumble upon unknown fields + break + } + data[names[i]] = f + } + + return data, nil +} + +func keyctlLink(parent, child int32) error { + _, _, errno := syscall.Syscall(syscall.SYS_KEYCTL, uintptr(unix.KEYCTL_LINK), uintptr(child), uintptr(parent)) + if errno != 0 { + return errno + } + return nil +} + +func keyctlUnlink(parent, child int32) error { + _, _, errno := syscall.Syscall(syscall.SYS_KEYCTL, uintptr(unix.KEYCTL_UNLINK), uintptr(child), uintptr(parent)) + if errno != 0 { + return errno + } + return nil +} + +func keyctlSetperm(id int32, perm uint32) error { + return unix.KeyctlSetperm(int(id), perm) +} + +func keyctlConvertKeyBuffer(buffer []byte) ([]int32, error) { + if len(buffer)%4 != 0 { + return nil, fmt.Errorf("buffer size %d not a multiple of 4", len(buffer)) + } + + results := make([]int32, 0, len(buffer)/4) + for i := 0; i < len(buffer); i += 4 { + // We need to case in host-native endianess here as this is what we get from the kernel. + r := *((*int32)(unsafe.Pointer(&buffer[i]))) + results = append(results, r) + } + return results, nil +} diff --git a/vendor/github.com/99designs/keyring/keyring.go b/vendor/github.com/99designs/keyring/keyring.go new file mode 100644 index 0000000..12161b7 --- /dev/null +++ b/vendor/github.com/99designs/keyring/keyring.go @@ -0,0 +1,134 @@ +// Package keyring provides a uniform API over a range of desktop credential storage engines. +package keyring + +import ( + "errors" + "log" + "time" +) + +// BackendType is an identifier for a credential storage service. +type BackendType string + +// All currently supported secure storage backends. +const ( + InvalidBackend BackendType = "" + SecretServiceBackend BackendType = "secret-service" + KeychainBackend BackendType = "keychain" + KeyCtlBackend BackendType = "keyctl" + KWalletBackend BackendType = "kwallet" + WinCredBackend BackendType = "wincred" + FileBackend BackendType = "file" + PassBackend BackendType = "pass" +) + +// This order makes sure the OS-specific backends +// are picked over the more generic backends. +var backendOrder = []BackendType{ + // Windows + WinCredBackend, + // MacOS + KeychainBackend, + // Linux + SecretServiceBackend, + KWalletBackend, + KeyCtlBackend, + // General + PassBackend, + FileBackend, +} + +var supportedBackends = map[BackendType]opener{} + +// AvailableBackends provides a slice of all available backend keys on the current OS. +func AvailableBackends() []BackendType { + b := []BackendType{} + for _, k := range backendOrder { + _, ok := supportedBackends[k] + if ok { + b = append(b, k) + } + } + return b +} + +type opener func(cfg Config) (Keyring, error) + +// Open will open a specific keyring backend. +func Open(cfg Config) (Keyring, error) { + if cfg.AllowedBackends == nil { + cfg.AllowedBackends = AvailableBackends() + } + debugf("Considering backends: %v", cfg.AllowedBackends) + for _, backend := range cfg.AllowedBackends { + if opener, ok := supportedBackends[backend]; ok { + openBackend, err := opener(cfg) + if err != nil { + debugf("Failed backend %s: %s", backend, err) + continue + } + return openBackend, nil + } + } + return nil, ErrNoAvailImpl +} + +// Item is a thing stored on the keyring. +type Item struct { + Key string + Data []byte + Label string + Description string + + // Backend specific config + KeychainNotTrustApplication bool + KeychainNotSynchronizable bool +} + +// Metadata is information about a thing stored on the keyring; retrieving +// metadata must not require authentication. The embedded Item should be +// filled in with an empty Data field. +// It's allowed for Item to be a nil pointer, indicating that all we +// have is the timestamps. +type Metadata struct { + *Item + ModificationTime time.Time +} + +// Keyring provides the uniform interface over the underlying backends. +type Keyring interface { + // Returns an Item matching the key or ErrKeyNotFound + Get(key string) (Item, error) + // Returns the non-secret parts of an Item + GetMetadata(key string) (Metadata, error) + // Stores an Item on the keyring + Set(item Item) error + // Removes the item with matching key + Remove(key string) error + // Provides a slice of all keys stored on the keyring + Keys() ([]string, error) +} + +// ErrNoAvailImpl is returned by Open when a backend cannot be found. +var ErrNoAvailImpl = errors.New("Specified keyring backend not available") + +// ErrKeyNotFound is returned by Keyring Get when the item is not on the keyring. +var ErrKeyNotFound = errors.New("The specified item could not be found in the keyring") + +// ErrMetadataNeedsCredentials is returned when Metadata is called against a +// backend which requires credentials even to see metadata. +var ErrMetadataNeedsCredentials = errors.New("The keyring backend requires credentials for metadata access") + +// ErrMetadataNotSupported is returned when Metadata is not available for the backend. +var ErrMetadataNotSupported = errors.New("The keyring backend does not support metadata access") + +var ( + // Debug specifies whether to print debugging output. + Debug bool +) + +func debugf(pattern string, args ...interface{}) { + if Debug { + log.Printf("[keyring] "+pattern, args...) + } +} diff --git a/vendor/github.com/99designs/keyring/kwallet.go b/vendor/github.com/99designs/keyring/kwallet.go new file mode 100644 index 0000000..771baba --- /dev/null +++ b/vendor/github.com/99designs/keyring/kwallet.go @@ -0,0 +1,237 @@ +//go:build linux +// +build linux + +package keyring + +import ( + "encoding/json" + "os" + + "github.com/godbus/dbus" +) + +const ( + dbusServiceName = "org.kde.kwalletd5" + dbusPath = "/modules/kwalletd5" +) + +func init() { + if os.Getenv("DISABLE_KWALLET") == "1" { + return + } + + // silently fail if dbus isn't available + _, err := dbus.SessionBus() + if err != nil { + return + } + + supportedBackends[KWalletBackend] = opener(func(cfg Config) (Keyring, error) { + if cfg.ServiceName == "" { + cfg.ServiceName = "kdewallet" + } + + if cfg.KWalletAppID == "" { + cfg.KWalletAppID = "keyring" + } + + if cfg.KWalletFolder == "" { + cfg.KWalletFolder = "keyring" + } + + wallet, err := newKwallet() + if err != nil { + return nil, err + } + + ring := &kwalletKeyring{ + wallet: *wallet, + name: cfg.ServiceName, + appID: cfg.KWalletAppID, + folder: cfg.KWalletFolder, + } + + return ring, ring.openWallet() + }) +} + +type kwalletKeyring struct { + wallet kwalletBinding + name string + handle int32 + appID string + folder string +} + +func (k *kwalletKeyring) openWallet() error { + isOpen, err := k.wallet.IsOpen(k.handle) + if err != nil { + return err + } + + if !isOpen { + handle, err := k.wallet.Open(k.name, 0, k.appID) + if err != nil { + return err + } + k.handle = handle + } + + return nil +} + +func (k *kwalletKeyring) Get(key string) (Item, error) { + err := k.openWallet() + if err != nil { + return Item{}, err + } + + data, err := k.wallet.ReadEntry(k.handle, k.folder, key, k.appID) + if err != nil { + return Item{}, err + } + if len(data) == 0 { + return Item{}, ErrKeyNotFound + } + + item := Item{} + err = json.Unmarshal(data, &item) + if err != nil { + return Item{}, err + } + + return item, nil +} + +// GetMetadata for kwallet returns an error indicating that it's unsupported +// for this backend. +// +// The only APIs found around KWallet are for retrieving content, no indication +// found in docs for methods to use to retrieve metadata without needing unlock +// credentials. +func (k *kwalletKeyring) GetMetadata(_ string) (Metadata, error) { + return Metadata{}, ErrMetadataNeedsCredentials +} + +func (k *kwalletKeyring) Set(item Item) error { + err := k.openWallet() + if err != nil { + return err + } + + data, err := json.Marshal(item) + if err != nil { + return err + } + + err = k.wallet.WriteEntry(k.handle, k.folder, item.Key, data, k.appID) + if err != nil { + return err + } + + return nil +} + +func (k *kwalletKeyring) Remove(key string) error { + err := k.openWallet() + if err != nil { + return err + } + + err = k.wallet.RemoveEntry(k.handle, k.folder, key, k.appID) + if err != nil { + return err + } + + return nil +} + +func (k *kwalletKeyring) Keys() ([]string, error) { + err := k.openWallet() + if err != nil { + return []string{}, err + } + + entries, err := k.wallet.EntryList(k.handle, k.folder, k.appID) + if err != nil { + return []string{}, err + } + + return entries, nil +} + +func newKwallet() (*kwalletBinding, error) { + conn, err := dbus.SessionBus() + if err != nil { + return nil, err + } + + return &kwalletBinding{ + conn.Object(dbusServiceName, dbusPath), + }, nil +} + +// Dumb Dbus bindings for kwallet bindings with types. +type kwalletBinding struct { + dbus dbus.BusObject +} + +// method bool org.kde.KWallet.isOpen(int handle) +func (k *kwalletBinding) IsOpen(handle int32) (bool, error) { + call := k.dbus.Call("org.kde.KWallet.isOpen", 0, handle) + if call.Err != nil { + return false, call.Err + } + + return call.Body[0].(bool), call.Err +} + +// method int org.kde.KWallet.open(QString wallet, qlonglong wId, QString appid) +func (k *kwalletBinding) Open(name string, wID int64, appid string) (int32, error) { + call := k.dbus.Call("org.kde.KWallet.open", 0, name, wID, appid) + if call.Err != nil { + return 0, call.Err + } + + return call.Body[0].(int32), call.Err +} + +// method QStringList org.kde.KWallet.entryList(int handle, QString folder, QString appid) +func (k *kwalletBinding) EntryList(handle int32, folder string, appid string) ([]string, error) { + call := k.dbus.Call("org.kde.KWallet.entryList", 0, handle, folder, appid) + if call.Err != nil { + return []string{}, call.Err + } + + return call.Body[0].([]string), call.Err +} + +// method int org.kde.KWallet.writeEntry(int handle, QString folder, QString key, QByteArray value, QString appid) +func (k *kwalletBinding) WriteEntry(handle int32, folder string, key string, value []byte, appid string) error { + call := k.dbus.Call("org.kde.KWallet.writeEntry", 0, handle, folder, key, value, appid) + if call.Err != nil { + return call.Err + } + + return call.Err +} + +// method int org.kde.KWallet.removeEntry(int handle, QString folder, QString key, QString appid) +func (k *kwalletBinding) RemoveEntry(handle int32, folder string, key string, appid string) error { + call := k.dbus.Call("org.kde.KWallet.removeEntry", 0, handle, folder, key, appid) + if call.Err != nil { + return call.Err + } + + return call.Err +} + +// method QByteArray org.kde.KWallet.readEntry(int handle, QString folder, QString key, QString appid) +func (k *kwalletBinding) ReadEntry(handle int32, folder string, key string, appid string) ([]byte, error) { + call := k.dbus.Call("org.kde.KWallet.readEntry", 0, handle, folder, key, appid) + if call.Err != nil { + return []byte{}, call.Err + } + + return call.Body[0].([]byte), call.Err +} diff --git a/vendor/github.com/99designs/keyring/pass.go b/vendor/github.com/99designs/keyring/pass.go new file mode 100644 index 0000000..5ca0bf0 --- /dev/null +++ b/vendor/github.com/99designs/keyring/pass.go @@ -0,0 +1,166 @@ +//go:build !windows +// +build !windows + +package keyring + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func init() { + supportedBackends[PassBackend] = opener(func(cfg Config) (Keyring, error) { + var err error + + pass := &passKeyring{ + passcmd: cfg.PassCmd, + dir: cfg.PassDir, + prefix: cfg.PassPrefix, + } + + if pass.passcmd == "" { + pass.passcmd = "pass" + } + + if pass.dir == "" { + if passDir, found := os.LookupEnv("PASSWORD_STORE_DIR"); found { + pass.dir = passDir + } else { + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, err + } + pass.dir = filepath.Join(homeDir, ".password-store") + } + } + + pass.dir, err = ExpandTilde(pass.dir) + if err != nil { + return nil, err + } + + // fail if the pass program is not available + _, err = exec.LookPath(pass.passcmd) + if err != nil { + return nil, errors.New("The pass program is not available") + } + + return pass, nil + }) +} + +type passKeyring struct { + dir string + passcmd string + prefix string +} + +func (k *passKeyring) pass(args ...string) *exec.Cmd { + cmd := exec.Command(k.passcmd, args...) + if k.dir != "" { + cmd.Env = append(os.Environ(), fmt.Sprintf("PASSWORD_STORE_DIR=%s", k.dir)) + } + cmd.Stderr = os.Stderr + + return cmd +} + +func (k *passKeyring) Get(key string) (Item, error) { + if !k.itemExists(key) { + return Item{}, ErrKeyNotFound + } + + name := filepath.Join(k.prefix, key) + cmd := k.pass("show", name) + output, err := cmd.Output() + if err != nil { + return Item{}, err + } + + var decoded Item + err = json.Unmarshal(output, &decoded) + + return decoded, err +} + +func (k *passKeyring) GetMetadata(key string) (Metadata, error) { + return Metadata{}, nil +} + +func (k *passKeyring) Set(i Item) error { + bytes, err := json.Marshal(i) + if err != nil { + return err + } + + name := filepath.Join(k.prefix, i.Key) + cmd := k.pass("insert", "-m", "-f", name) + cmd.Stdin = strings.NewReader(string(bytes)) + + err = cmd.Run() + if err != nil { + return err + } + + return nil +} + +func (k *passKeyring) Remove(key string) error { + if !k.itemExists(key) { + return ErrKeyNotFound + } + + name := filepath.Join(k.prefix, key) + cmd := k.pass("rm", "-f", name) + err := cmd.Run() + if err != nil { + return err + } + + return nil +} + +func (k *passKeyring) itemExists(key string) bool { + var path = filepath.Join(k.dir, k.prefix, key+".gpg") + _, err := os.Stat(path) + + return err == nil +} + +func (k *passKeyring) Keys() ([]string, error) { + var keys = []string{} + var path = filepath.Join(k.dir, k.prefix) + + info, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return keys, nil + } + return keys, err + } + if !info.IsDir() { + return keys, fmt.Errorf("%s is not a directory", path) + } + + err = filepath.Walk(path, func(p string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if !info.IsDir() && filepath.Ext(p) == ".gpg" { + name := strings.TrimPrefix(p, path) + if name[0] == os.PathSeparator { + name = name[1:] + } + keys = append(keys, name[:len(name)-4]) + } + return nil + }) + + return keys, err +} diff --git a/vendor/github.com/99designs/keyring/prompt.go b/vendor/github.com/99designs/keyring/prompt.go new file mode 100644 index 0000000..59ad81c --- /dev/null +++ b/vendor/github.com/99designs/keyring/prompt.go @@ -0,0 +1,27 @@ +package keyring + +import ( + "fmt" + "os" + + "golang.org/x/term" +) + +// PromptFunc is a function used to prompt the user for a password. +type PromptFunc func(string) (string, error) + +func TerminalPrompt(prompt string) (string, error) { + fmt.Printf("%s: ", prompt) + b, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + return "", err + } + fmt.Println() + return string(b), nil +} + +func FixedStringPrompt(value string) PromptFunc { + return func(_ string) (string, error) { + return value, nil + } +} diff --git a/vendor/github.com/99designs/keyring/secretservice.go b/vendor/github.com/99designs/keyring/secretservice.go new file mode 100644 index 0000000..2a1f9d9 --- /dev/null +++ b/vendor/github.com/99designs/keyring/secretservice.go @@ -0,0 +1,293 @@ +//go:build linux +// +build linux + +package keyring + +import ( + "encoding/hex" + "encoding/json" + "errors" + + "strings" + + "github.com/godbus/dbus" + "github.com/gsterjov/go-libsecret" +) + +func init() { + // silently fail if dbus isn't available + _, err := dbus.SessionBus() + if err != nil { + return + } + + supportedBackends[SecretServiceBackend] = opener(func(cfg Config) (Keyring, error) { + if cfg.ServiceName == "" { + cfg.ServiceName = "secret-service" + } + if cfg.LibSecretCollectionName == "" { + cfg.LibSecretCollectionName = cfg.ServiceName + } + + service, err := libsecret.NewService() + if err != nil { + return &secretsKeyring{}, err + } + + ring := &secretsKeyring{ + name: cfg.LibSecretCollectionName, + service: service, + } + + return ring, ring.openSecrets() + }) +} + +type secretsKeyring struct { + name string + service *libsecret.Service + collection *libsecret.Collection + session *libsecret.Session +} + +var errCollectionNotFound = errors.New("The collection does not exist. Please add a key first") + +func decodeKeyringString(src string) string { + var dst strings.Builder + for i := 0; i < len(src); i++ { + if src[i] != '_' { + dst.WriteString(string(src[i])) + } else { + if i+3 > len(src) { + return src + } + hexstring := src[i+1 : i+3] + decoded, err := hex.DecodeString(hexstring) + if err != nil { + return src + } + dst.Write(decoded) + i += 2 + } + } + return dst.String() +} + +func (k *secretsKeyring) openSecrets() error { + session, err := k.service.Open() + if err != nil { + return err + } + k.session = session + + // get the collection if it already exists + collections, err := k.service.Collections() + if err != nil { + return err + } + + path := libsecret.DBusPath + "/collection/" + k.name + + for _, collection := range collections { + if decodeKeyringString(string(collection.Path())) == path { + c := collection // fix variable into the local variable to ensure it's referenced correctly, see https://github.com/kyoh86/exportloopref + k.collection = &c + return nil + } + } + + return nil +} + +func (k *secretsKeyring) openCollection() error { + if err := k.openSecrets(); err != nil { + return err + } + + if k.collection == nil { + return errCollectionNotFound + // return &secretsError{fmt.Sprintf( + // "The collection %q does not exist. Please add a key first", + // k.name, + // )} + } + + return nil +} + +func (k *secretsKeyring) Get(key string) (Item, error) { + if err := k.openCollection(); err != nil { + if err == errCollectionNotFound { + return Item{}, ErrKeyNotFound + } + return Item{}, err + } + + items, err := k.collection.SearchItems(key) + if err != nil { + return Item{}, err + } + + if len(items) == 0 { + return Item{}, ErrKeyNotFound + } + + // use the first item whenever there are multiples + // with the same profile name + item := items[0] + + locked, err := item.Locked() + if err != nil { + return Item{}, err + } + + if locked { + if err := k.service.Unlock(item); err != nil { + return Item{}, err + } + } + + secret, err := item.GetSecret(k.session) + if err != nil { + return Item{}, err + } + + // pack the secret into the item + var ret Item + if err = json.Unmarshal(secret.Value, &ret); err != nil { + return Item{}, err + } + + return ret, err +} + +// GetMetadata for libsecret returns an error indicating that it's unsupported +// for this backend. +// +// libsecret actually implements a metadata system which we could use, "Secret +// Attributes"; I found no indication in documentation of anything like an +// automatically maintained last-modification timestamp, so to use this we'd +// need to have a SetMetadata API too. Which we're not yet doing, but feel +// free to contribute patches. +func (k *secretsKeyring) GetMetadata(key string) (Metadata, error) { + return Metadata{}, ErrMetadataNeedsCredentials +} + +func (k *secretsKeyring) Set(item Item) error { + err := k.openSecrets() + if err != nil { + return err + } + + // create the collection if it doesn't already exist + if k.collection == nil { + collection, err := k.service.CreateCollection(k.name) + if err != nil { + return err + } + + k.collection = collection + } + + if err := k.ensureCollectionUnlocked(); err != nil { + return err + } + + // create the new item + data, err := json.Marshal(item) + if err != nil { + return err + } + + secret := libsecret.NewSecret(k.session, []byte{}, data, "application/json") + + if _, err := k.collection.CreateItem(item.Key, secret, true); err != nil { + return err + } + + return nil +} + +func (k *secretsKeyring) Remove(key string) error { + if err := k.openCollection(); err != nil { + if err == errCollectionNotFound { + return ErrKeyNotFound + } + return err + } + + items, err := k.collection.SearchItems(key) + if err != nil { + return err + } + + // nothing to delete + if len(items) == 0 { + return nil + } + + // we dont want to delete more than one anyway + // so just get the first item found + item := items[0] + + locked, err := item.Locked() + if err != nil { + return err + } + + if locked { + if err := k.service.Unlock(item); err != nil { + return err + } + } + + if err := item.Delete(); err != nil { + return err + } + + return nil +} + +func (k *secretsKeyring) Keys() ([]string, error) { + if err := k.openCollection(); err != nil { + if err == errCollectionNotFound { + return []string{}, nil + } + return nil, err + } + if err := k.ensureCollectionUnlocked(); err != nil { + return nil, err + } + items, err := k.collection.Items() + if err != nil { + return nil, err + } + keys := []string{} + for _, item := range items { + label, err := item.Label() // FIXME: err is being silently ignored + if err == nil { + keys = append(keys, label) + } + } + return keys, nil +} + +// deleteCollection deletes the keyring's collection if it exists. This is mainly to support testing. +func (k *secretsKeyring) deleteCollection() error { + if err := k.openCollection(); err != nil { + return err + } + return k.collection.Delete() +} + +// unlock the collection if it's locked +func (k *secretsKeyring) ensureCollectionUnlocked() error { + locked, err := k.collection.Locked() + if err != nil { + return err + } + if !locked { + return nil + } + return k.service.Unlock(k.collection) +} diff --git a/vendor/github.com/99designs/keyring/tilde.go b/vendor/github.com/99designs/keyring/tilde.go new file mode 100644 index 0000000..c1847a6 --- /dev/null +++ b/vendor/github.com/99designs/keyring/tilde.go @@ -0,0 +1,22 @@ +package keyring + +import ( + "os" + "path/filepath" + "strings" +) + +var tildePrefix = string([]rune{'~', filepath.Separator}) + +// ExpandTilde will expand tilde (~/ or ~\ depending on OS) for the user home directory. +func ExpandTilde(dir string) (string, error) { + if strings.HasPrefix(dir, tildePrefix) { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", err + } + dir = strings.Replace(dir, "~", homeDir, 1) + debugf("Expanded file dir to %s", dir) + } + return dir, nil +} diff --git a/vendor/github.com/99designs/keyring/wincred.go b/vendor/github.com/99designs/keyring/wincred.go new file mode 100644 index 0000000..2ed43f2 --- /dev/null +++ b/vendor/github.com/99designs/keyring/wincred.go @@ -0,0 +1,98 @@ +//go:build windows +// +build windows + +package keyring + +import ( + "strings" + "syscall" + + "github.com/danieljoos/wincred" +) + +// ERROR_NOT_FOUND from https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--1000-1299- +const elementNotFoundError = syscall.Errno(1168) + +type windowsKeyring struct { + name string + prefix string +} + +func init() { + supportedBackends[WinCredBackend] = opener(func(cfg Config) (Keyring, error) { + name := cfg.ServiceName + if name == "" { + name = "default" + } + + prefix := cfg.WinCredPrefix + if prefix == "" { + prefix = "keyring" + } + + return &windowsKeyring{ + name: name, + prefix: prefix, + }, nil + }) +} + +func (k *windowsKeyring) Get(key string) (Item, error) { + cred, err := wincred.GetGenericCredential(k.credentialName(key)) + if err != nil { + if err == elementNotFoundError { + return Item{}, ErrKeyNotFound + } + return Item{}, err + } + + item := Item{ + Key: key, + Data: cred.CredentialBlob, + } + + return item, nil +} + +// GetMetadata for pass returns an error indicating that it's unsupported +// for this backend. +// TODO: This is a stub. Look into whether pass would support metadata in a usable way for keyring. +func (k *windowsKeyring) GetMetadata(_ string) (Metadata, error) { + return Metadata{}, ErrMetadataNotSupported +} + +func (k *windowsKeyring) Set(item Item) error { + cred := wincred.NewGenericCredential(k.credentialName(item.Key)) + cred.CredentialBlob = item.Data + return cred.Write() +} + +func (k *windowsKeyring) Remove(key string) error { + cred, err := wincred.GetGenericCredential(k.credentialName(key)) + if err != nil { + if err == elementNotFoundError { + return ErrKeyNotFound + } + return err + } + return cred.Delete() +} + +func (k *windowsKeyring) Keys() ([]string, error) { + results := []string{} + + if creds, err := wincred.List(); err == nil { + for _, cred := range creds { + prefix := k.credentialName("") + if strings.HasPrefix(cred.TargetName, prefix) { + results = append(results, strings.TrimPrefix(cred.TargetName, prefix)) + } + } + } + + return results, nil +} + +func (k *windowsKeyring) credentialName(key string) string { + return k.prefix + ":" + k.name + ":" + key +} |
