mirror of
https://github.com/ProtonMail/go-keychain.git
synced 2026-01-11 19:58:20 +00:00
Add support for internet password items (#95)
This commit is contained in:
parent
3e4884637d
commit
4f19d685f1
3 changed files with 138 additions and 10 deletions
|
|
@ -101,6 +101,13 @@ func CFDictionaryToMap(cfDict C.CFDictionaryRef) (m map[C.CFTypeRef]C.CFTypeRef)
|
|||
return
|
||||
}
|
||||
|
||||
// Int32ToCFNumber will return a CFNumberRef, must be released with Release(ref).
|
||||
func Int32ToCFNumber(u int32) C.CFNumberRef {
|
||||
sint := C.SInt32(u)
|
||||
p := unsafe.Pointer(&sint)
|
||||
return C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberSInt32Type, p)
|
||||
}
|
||||
|
||||
// StringToCFString will return a CFStringRef and if non-nil, must be released with
|
||||
// Release(ref).
|
||||
func StringToCFString(s string) (C.CFStringRef, error) {
|
||||
|
|
@ -186,6 +193,9 @@ func ConvertMapToCFDictionary(attr map[string]interface{}) (C.CFDictionaryRef, e
|
|||
} else {
|
||||
valueRef = C.CFTypeRef(C.kCFBooleanFalse)
|
||||
}
|
||||
case int32:
|
||||
valueRef = C.CFTypeRef(Int32ToCFNumber(val))
|
||||
defer Release(valueRef)
|
||||
case []byte:
|
||||
bytesRef, err := BytesToCFData(val)
|
||||
if err != nil {
|
||||
|
|
|
|||
81
keychain.go
81
keychain.go
|
|
@ -157,6 +157,18 @@ var secClassTypeRef = map[SecClass]C.CFTypeRef{
|
|||
var (
|
||||
// ServiceKey is for kSecAttrService
|
||||
ServiceKey = attrKey(C.CFTypeRef(C.kSecAttrService))
|
||||
|
||||
// ServerKey is for kSecAttrServer
|
||||
ServerKey = attrKey(C.CFTypeRef(C.kSecAttrServer))
|
||||
// ProtocolKey is for kSecAttrProtocol
|
||||
ProtocolKey = attrKey(C.CFTypeRef(C.kSecAttrProtocol))
|
||||
// AuthenticationTypeKey is for kSecAttrAuthenticationType
|
||||
AuthenticationTypeKey = attrKey(C.CFTypeRef(C.kSecAttrAuthenticationType))
|
||||
// PortKey is for kSecAttrPort
|
||||
PortKey = attrKey(C.CFTypeRef(C.kSecAttrPort))
|
||||
// PathKey is for kSecAttrPath
|
||||
PathKey = attrKey(C.CFTypeRef(C.kSecAttrPath))
|
||||
|
||||
// LabelKey is for kSecAttrLabel
|
||||
LabelKey = attrKey(C.CFTypeRef(C.kSecAttrLabel))
|
||||
// AccountKey is for kSecAttrAccount
|
||||
|
|
@ -167,6 +179,8 @@ var (
|
|||
DataKey = attrKey(C.CFTypeRef(C.kSecValueData))
|
||||
// DescriptionKey is for kSecAttrDescription
|
||||
DescriptionKey = attrKey(C.CFTypeRef(C.kSecAttrDescription))
|
||||
// CommentKey is for kSecAttrComment
|
||||
CommentKey = attrKey(C.CFTypeRef(C.kSecAttrComment))
|
||||
// CreationDateKey is for kSecAttrCreationDate
|
||||
CreationDateKey = attrKey(C.CFTypeRef(C.kSecAttrCreationDate))
|
||||
// ModificationDateKey is for kSecAttrModificationDate
|
||||
|
|
@ -256,6 +270,15 @@ func (k *Item) SetSecClass(sc SecClass) {
|
|||
k.attr[SecClassKey] = secClassTypeRef[sc]
|
||||
}
|
||||
|
||||
// SetInt32 sets an int32 attribute for a string key
|
||||
func (k *Item) SetInt32(key string, v int32) {
|
||||
if v != 0 {
|
||||
k.attr[key] = v
|
||||
} else {
|
||||
delete(k.attr, key)
|
||||
}
|
||||
}
|
||||
|
||||
// SetString sets a string attibute for a string key
|
||||
func (k *Item) SetString(key string, s string) {
|
||||
if s != "" {
|
||||
|
|
@ -265,11 +288,37 @@ func (k *Item) SetString(key string, s string) {
|
|||
}
|
||||
}
|
||||
|
||||
// SetService sets the service attribute
|
||||
// SetService sets the service attribute (for generic application items)
|
||||
func (k *Item) SetService(s string) {
|
||||
k.SetString(ServiceKey, s)
|
||||
}
|
||||
|
||||
// SetServer sets the server attribute (for internet password items)
|
||||
func (k *Item) SetServer(s string) {
|
||||
k.SetString(ServerKey, s)
|
||||
}
|
||||
|
||||
// SetProtocol sets the protocol attribute (for internet password items)
|
||||
// Example values are: "htps", "http", "smb "
|
||||
func (k *Item) SetProtocol(s string) {
|
||||
k.SetString(ProtocolKey, s)
|
||||
}
|
||||
|
||||
// SetAuthenticationType sets the authentication type attribute (for internet password items)
|
||||
func (k *Item) SetAuthenticationType(s string) {
|
||||
k.SetString(AuthenticationTypeKey, s)
|
||||
}
|
||||
|
||||
// SetPort sets the port attribute (for internet password items)
|
||||
func (k *Item) SetPort(v int32) {
|
||||
k.SetInt32(PortKey, v)
|
||||
}
|
||||
|
||||
// SetPath sets the path attribute (for internet password items)
|
||||
func (k *Item) SetPath(s string) {
|
||||
k.SetString(PathKey, s)
|
||||
}
|
||||
|
||||
// SetAccount sets the account attribute
|
||||
func (k *Item) SetAccount(a string) {
|
||||
k.SetString(AccountKey, a)
|
||||
|
|
@ -285,6 +334,11 @@ func (k *Item) SetDescription(s string) {
|
|||
k.SetString(DescriptionKey, s)
|
||||
}
|
||||
|
||||
// SetComment sets the comment attribute
|
||||
func (k *Item) SetComment(s string) {
|
||||
k.SetString(CommentKey, s)
|
||||
}
|
||||
|
||||
// SetData sets the data attribute
|
||||
func (k *Item) SetData(b []byte) {
|
||||
if b != nil {
|
||||
|
|
@ -391,11 +445,21 @@ func UpdateItem(queryItem Item, updateItem Item) error {
|
|||
// QueryResult stores all possible results from queries.
|
||||
// Not all fields are applicable all the time. Results depend on query.
|
||||
type QueryResult struct {
|
||||
Service string
|
||||
// For generic application items
|
||||
Service string
|
||||
|
||||
// For internet password items
|
||||
Server string
|
||||
Protocol string
|
||||
AuthenticationType string
|
||||
Port int32
|
||||
Path string
|
||||
|
||||
Account string
|
||||
AccessGroup string
|
||||
Label string
|
||||
Description string
|
||||
Comment string
|
||||
Data []byte
|
||||
CreationDate time.Time
|
||||
ModificationDate time.Time
|
||||
|
|
@ -480,6 +544,17 @@ func convertResult(d C.CFDictionaryRef) (*QueryResult, error) {
|
|||
switch attrKey(k) {
|
||||
case ServiceKey:
|
||||
result.Service = CFStringToString(C.CFStringRef(v))
|
||||
case ServerKey:
|
||||
result.Server = CFStringToString(C.CFStringRef(v))
|
||||
case ProtocolKey:
|
||||
result.Protocol = CFStringToString(C.CFStringRef(v))
|
||||
case AuthenticationTypeKey:
|
||||
result.AuthenticationType = CFStringToString(C.CFStringRef(v))
|
||||
case PortKey:
|
||||
val := CFNumberToInterface(C.CFNumberRef(v))
|
||||
result.Port = val.(int32)
|
||||
case PathKey:
|
||||
result.Path = CFStringToString(C.CFStringRef(v))
|
||||
case AccountKey:
|
||||
result.Account = CFStringToString(C.CFStringRef(v))
|
||||
case AccessGroupKey:
|
||||
|
|
@ -488,6 +563,8 @@ func convertResult(d C.CFDictionaryRef) (*QueryResult, error) {
|
|||
result.Label = CFStringToString(C.CFStringRef(v))
|
||||
case DescriptionKey:
|
||||
result.Description = CFStringToString(C.CFStringRef(v))
|
||||
case CommentKey:
|
||||
result.Comment = CFStringToString(C.CFStringRef(v))
|
||||
case DataKey:
|
||||
b, err := CFDataToBytes(C.CFDataRef(v))
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
package keychain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -86,18 +85,60 @@ func TestGenericPasswordRef(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInternetPassword(t *testing.T) {
|
||||
item := NewItem()
|
||||
item.SetSecClass(SecClassInternetPassword)
|
||||
|
||||
// Internet password-specific attributes
|
||||
item.SetProtocol("htps")
|
||||
item.SetServer("8xs8h5x5dfc0AI5EzT81l.com")
|
||||
item.SetPort(1234)
|
||||
item.SetPath("/this/is/the/path")
|
||||
|
||||
item.SetAccount("this-is-the-username")
|
||||
item.SetLabel("this is the label")
|
||||
item.SetData([]byte("this is the password"))
|
||||
item.SetComment("this is the comment")
|
||||
defer func() { _ = DeleteItem(item) }()
|
||||
err := AddItem(item)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
query := NewItem()
|
||||
query.SetSecClass(SecClassInternetPassword)
|
||||
query.SetLabel("github.com")
|
||||
query.SetServer("8xs8h5x5dfc0AI5EzT81l.com")
|
||||
query.SetMatchLimit(MatchLimitOne)
|
||||
query.SetReturnAttributes(true)
|
||||
results, err := QueryItem(query)
|
||||
if err != nil {
|
||||
// Error
|
||||
t.Errorf("Query Error: %v", err)
|
||||
} else {
|
||||
for _, r := range results {
|
||||
fmt.Printf("%#v\n", r.Account)
|
||||
}
|
||||
t.Fatalf("Query Error: %v", err)
|
||||
}
|
||||
|
||||
if len(results) != 1 {
|
||||
t.Fatalf("expected 1 result, got %d", len(results))
|
||||
}
|
||||
|
||||
r := results[0]
|
||||
if r.Protocol != "htps" {
|
||||
t.Errorf("expected protocol 'htps' but got %q", r.Protocol)
|
||||
}
|
||||
if r.Server != "8xs8h5x5dfc0AI5EzT81l.com" {
|
||||
t.Errorf("expected server '8xs8h5x5dfc0AI5EzT81l.com' but got %q", r.Server)
|
||||
}
|
||||
if r.Port != 1234 {
|
||||
t.Errorf("expected port '1234' but got %d", r.Port)
|
||||
}
|
||||
if r.Path != "/this/is/the/path" {
|
||||
t.Errorf("expected path '/this/is/the/path' but got %q", r.Path)
|
||||
}
|
||||
|
||||
if r.Account != "this-is-the-username" {
|
||||
t.Errorf("expected account 'this-is-the-username' but got %q", r.Account)
|
||||
}
|
||||
if r.Label != "this is the label" {
|
||||
t.Errorf("expected label 'this is the label' but got %q", r.Label)
|
||||
}
|
||||
if r.Comment != "this is the comment" {
|
||||
t.Errorf("expected comment 'this is the comment' but got %q", r.Comment)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue