Skip to content

Commit

Permalink
Add support for internet password items (keybase#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
pete-woods authored May 18, 2023
1 parent 3e48846 commit 4f19d68
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 10 deletions.
10 changes: 10 additions & 0 deletions corefoundation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
81 changes: 79 additions & 2 deletions keychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 != "" {
Expand All @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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 {
Expand Down
57 changes: 49 additions & 8 deletions macos_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package keychain

import (
"fmt"
"testing"
)

Expand Down Expand Up @@ -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)
}
}

0 comments on commit 4f19d68

Please sign in to comment.