Forum > Other OS
macOS Keychain access
(1/1)
CCRDude:
I'm working on improving my small UI for the Wireguard command line tools on macOS (reason: official Wireguard UI allows only one connection at a time, CLI allows more).
The regular UI stores profiles in keychain, the CLI uses the regular Wireguard configuration file location. It's easy to create these, but I want to provide a function to "import" profiles from the keychain.
It's easy to grab the first entry:
--- Code: ---function GetFirstWireguardProfile(out AProfile: ansistring): boolean;
var
s: OSStatus;
iMaxLen: uint32;
iActualLen: uint32;
pc: pansichar;
item: KCItemRefPtr;
str: Str255;
begin
// function KCFindGenericPassword( serviceName: ConstStringPtr { can be NULL }; accountName: ConstStringPtr { can be NULL }; maxLength: UInt32; passwordData: UnivPtr; var actualLength: UInt32; item: KCItemRefPtr { can be NULL } ): OSStatus; external name '_KCFindGenericPassword';
// @see https://developer.apple.com/documentation/coreservices/1562994-kcfindgenericpassword
str := 'com.wireguard.macos';
iMaxLen := 1024;
iActualLen := 0;
pc := AllocMem(iMaxLen);
try
item := nil;
s := KCFindGenericPassword(@str, nil, iMaxLen, pc, iActualLen, item);
Result := (0 = s);
if Result then begin
AProfile := pc;
end;
finally
FreeMem(pc);
end;
end;
--- End code ---
Of course I want to access more than the first, so I'm looking for the right API calls. Has anyone had success with items like KCFindFirstItem? Even Google doesn't seem to find examples, regardless of the language.
My approach, not working yet, because I need to find out how to define search parameters most likely:
--- Code: ---function GetWireguardProfiles(constref AList: TStrings): boolean;
var
s: OSStatus;
keychain: KCRef;
attr: KCAttributeListPtr;
search: KCSearchRef;
item: KCItemRef;
iMaxLen: uint32;
iActualLen: uint32;
pc: pansichar;
begin
s := KCGetDefaultKeychain(keychain);
AList.Add(Format('KCGetDefaultKeychain() = %d', [s]));
// attr
attr := nil;
// search
search := nil;
item := nil;
s := KCFindFirstItem(keychain, attr, search, item);
AList.Add(Format('KCFindFirstItem() = %d', [s]));
iMaxLen := 1024;
iActualLen := 0;
pc := AllocMem(iMaxLen);
try
item := nil;
s := KCGetData(item, iMaxLen, pc, iActualLen);
AList.Add(Format('KCGetData(maxLength = %d, actualLength = %d) = %d', [iMaxLen, iActualLen, s]));
if (0 = s) then begin
AList.Add(AnsiString(pc));
end;
finally
FreeMem(pc);
end;
end;
--- End code ---
(code will go into this repository, Wireguard tool will go into separate repository)
CCRDude:
Update: it was easier than I thought...
Some further fine tuning required (restricting search to Wireguard service name), but the basic keychain access works.
--- Code: ---function GetWireguardProfiles(constref AList: TStrings): boolean;
var
s: OSStatus;
keychain: KCRef;
search: KCSearchRef;
item: KCItemRef;
iMaxLen: uint32;
iActualLen: uint32;
pc: pansichar;
attrList: KCAttributeList;
attra: array[0..1] of KCAttribute;
attr1: KCAttribute;
itemClass: KCItemClass;
sService: string;
begin
Result := True;
Initialize(keychain);
s := KCGetDefaultKeychain(keychain);
// AList.Add(Format('KCGetDefaultKeychain() = %d', [s]));
// search
search := nil;
item := nil;
itemClass := kGenericPasswordKCItemClass;
attra[0].tag := kClassKCItemAttr;
attra[0].Data := @itemClass;
attra[0].length := sizeof(itemClass);
// TODO : restrict search to service
attrList.Count := 1;
attrList.attr := @attra[0];
// @see https://github.com/aptana/Jaxer/blob/f7994fc75a768c9873f094e29868c22e88b46b50/server/src/mozilla/extensions/wallet/src/singsign.cpp#L3204
s := KCFindFirstItem(keychain, @attrList, search, item);
// AList.Add(Format('KCFindFirstItem() = %d', [s]));
repeat
iMaxLen := 1024;
iActualLen := 0;
pc := AllocMem(iMaxLen);
try
attr1.tag := kServiceKCItemAttr;
attr1.length := iMaxLen;
attr1.Data := pc;
s := KCGetAttribute(item, attr1, iActualLen);
if (s = 0) then begin
sService := ansistring(pc);
// AList.Add(Format('KCGetAttribute() = %d, server = %s', [s, sService]));
if ('com.wireguard.macos' = sService) then begin
s := KCGetData(item, iMaxLen, pc, iActualLen);
// AList.Add(Format('KCGetData(maxLength = %d, actualLength = %d) = %d', [iMaxLen, iActualLen, s]));
if (0 = s) then begin
AList.Add(ansistring(pc));
end;
end;
end else begin
// AList.Add(Format('KCGetAttribute() = %d', [s]));
end;
finally
FreeMem(pc);
end;
s := KCFindNextItem(search, item);
// AList.Add(Format('KCFindNextItem() = %d', [s]));
until (s <> 0);
end;
--- End code ---
CCRDude:
Public repository now includes code to read all entries for a specific service (for me: com.wireguard.macos):
https://gitlab.com/ccrdude-pascal/firefly-macos/-/blob/main/source/Firefly.MacOS.KeyChain.pas
cdbc:
Hi
Just curious, you have "KCFindFirstItem()" & "KCFindNextItem()", is it just me that wants a "KCFindClose()" somewhere at the end?!?
I guess, I'm just too used to "FindFirst", "FindNext" & "FindClose" from file-searching....
Pardon
Benny
CCRDude:
You're absolutely right, it still needs a KCReleaseSearch and even KCReleaseItem calls. These have been added now!
Due to the Apple documentation pages about these calls being empty, there's some guessing involved, but I finally found some examples searching on GitHub, and these also use them.
Navigation
[0] Message Index