Recent

Author Topic: macOS Keychain access  (Read 1254 times)

CCRDude

  • Hero Member
  • *****
  • Posts: 596
macOS Keychain access
« on: January 24, 2024, 11:28:07 am »
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: [Select]
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;

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: [Select]
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; 

(code will go into this repository, Wireguard tool will go into separate repository)
« Last Edit: January 24, 2024, 12:51:44 pm by CCRDude »

CCRDude

  • Hero Member
  • *****
  • Posts: 596
Re: Keychain access
« Reply #1 on: January 24, 2024, 12:02:17 pm »
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: [Select]
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;

CCRDude

  • Hero Member
  • *****
  • Posts: 596
Re: Keychain access
« Reply #2 on: January 24, 2024, 12:51:25 pm »
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

  • Hero Member
  • *****
  • Posts: 970
    • http://www.cdbc.dk
Re: macOS Keychain access
« Reply #3 on: January 24, 2024, 01:32:09 pm »
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
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

CCRDude

  • Hero Member
  • *****
  • Posts: 596
Re: macOS Keychain access
« Reply #4 on: January 24, 2024, 04:53:39 pm »
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.

 

TinyPortal © 2005-2018