Running privilaged tasks in pure swift app

Hi developers, i need some help to let my app to work as it's supposed to do in the guidelines, i am creating a swift app which is a GUI for a command line tool but this command line tool does require to be run with sudo or by the root user, so to run it am temporarely using a not safe system which is just using a Process object to lunch a script with the sh executable to aks the user for the sudo password and then give that poassowrd to the sudo command and launch the command line tool, but i know that the proper way to that is by using an xpc service and then let the xpc service to do all the privilaged job, whithou having my app to deal with user credentials, but i need something for pure swift apps, i understand little to nothing of objective-c and i am also making this app in pure swift to be future proof, so i need you help to make that possible using pure swift.


the only things i have found are the autherization samples from apple which are in objective-c only and as i mentioned, i do not understand objective-c.


i need that the xpc service starts in a precise moment, when the uses clicks a specific button and then the app should pass the location of the command line tool, and the arguments to run it to the xpc service, and the wait until the xpc service ends it's job of running the executable, which can take also several minutes, after it's finished it will give back to the app some information, which are the full output ot the command lind tool and then it's exit code, becuase i need that data to determinate if the has done correctly his job and in case of error, which error it is, and to determinate that i use a combination of output analisys and kown exit codes.


And also i preffer that the code is swift 3 compatible because i am stiil using xcode 8, and it should run also on yosemite which is one of mine development targets, because much potetial users for my app are still running older versions of macOS.


the command line tool is not made by me and it's closed source, it's the "createinstallmedia" executable from apple's mac os installer apps, and i am attempting to create a tool to create a mac os bootable usb installer in a noob-friendly way using apple's official method and to pubblish it on the app store when it will be ready, and of course it will be open source and free for everyone.


So i'd like much to have your help because i couldn't find anything elsewhere, thank very much for your interest in helping me.

The standard sample for this is EvenBetterAuthorizationSample. Yes, it’s written in Objective-C but that should not be a major stumbling block in my opinion. This stuff is sufficiently complex that learning enough Objective-C to get a handle on the code is a small task compared to understanding the technique as a whole. Or, put another way, an experienced Objective-C programmer is likely to have similar levels of difficulty here.

As far as I know there’s nothing that will prevent you from implementing this technique in Swift. There are, however, some challenges. For example:

  • NSXPCConnection
    has some sharp edges when you use it from Swift. The good news here is that the specific sharp edge I’m aware of, calling
    -setClasses:forSelector:argumentIndex:ofReply:
    , shouldn’t be necessary in this case.

    Note If you ever do need to do this, here’s how you might implement it.

    let expectedClasses: NSSet = [MyCustomClass.self]
    remoteObjectInterface.setClasses(
        expectedClasses as! Set<AnyHashable>, 
        for: #selector(MyHelperProtocol.myMethod(arg1:…)), 
        argumentIndex: …, 
        ofReply: …
    )

    .

  • Authorization Services is a low-level C API, so you’re going to need to brush up on your calling-C-from-Swift skills. For example, here’s how you might call

    AuthorizationCopyRights
    from Swift:
    func authorizationCopyRight(name: String) -> OSStatus {
        return name.withCString { (namePtr) -> OSStatus in
            var oneRight = AuthorizationItem(name: namePtr, valueLength: 0, value: nil, flags: 0)
            return withUnsafeMutablePointer(to: &oneRight) { (oneRightPtr) -> OSStatus in
                var rights = [AuthorizationRights(count: 1, items: oneRightPtr)]
                return AuthorizationCopyRights(authRef, &rights, nil, [.extendRights, .interactionAllowed], nil)
            }
        }
    }

    .

If you have other specific questions about this technique I’d be happy to answer them.

Finally, you wrote:

the command line tool is not made by me and it's closed source

You’re going to need two command-line tools here:

  • The one you write, in Swift, that acts as your privileged helper tool.

  • The closed source one you want to execute, which will end up being run by your privileged helper tool.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

i have already found a way to create the authorization, but i don't know how to continue, i am a total noob for xpc services or helper tools.


I leave to you the code i put together to get the auth done, but i don't know what i should do to implement everithing i need, i need something like a noob oriented guide or somthing linke that and i really don't like the lack of swift documentation for that, please if you apple engeneers can, write also a swift documentation for privilaged tasks in swift only apps, it will be very usefould for a lot of developers.


//variables used to check the sucess of the authentication
    private var osStatus: OSStatus = 0
    private var osStatus2: OSStatus = 0
  
    //references we need to free the permitions later
    private var authRef2: AuthorizationRef?
    private var authFlags = AuthorizationFlags([])

private func askFirstAuthOldWay() -> Bool{
        let b = Bundle.main
     
        var authRef: AuthorizationRef? = nil
     
        self.osStatus = AuthorizationCreate(nil, nil, self.authFlags, &authRef)
        var myItems = [
            AuthorizationItem(name: b.bundleIdentifier! + ".sudo", valueLength: 0, value: nil, flags: 0),
            AuthorizationItem(name: b.bundleIdentifier! + ".createinstallmedia", valueLength: 0, value: nil, flags: 0)
        ]
        var myRights = AuthorizationRights(count: UInt32(myItems.count), items: &myItems)
        let myFlags : AuthorizationFlags = [.interactionAllowed, .extendRights, .destroyRights, .preAuthorize]
     
        self.osStatus2 = AuthorizationCreate(&myRights, nil, myFlags, &self.authRef2)
     
        return (self.osStatus == 0 && self.osStatus2 == 0)
    }

//this functrion frees the auth
    private func freeAuth(){
        
                if authRef2 != nil && !authFlags.isEmpty{
                    //we no longer need the special authorization, so it is freed
                    if AuthorizationFree(authRef2!, authFlags) == 0{
                        DispatchQueue.main.async {
                            log("AutorizationFree executed successfully")
                        }
                    }else{
                        DispatchQueue.main.async {
                            log("AutorizationFree failed")
                        }
                    }
                }
    }

Note that the log("sting stuff") function does implement the print function with some other features i use to display this outpout to the user in a log window, it does not affects this code obviusly, but because it is a code from the main thread of the app and the authentication stuff is done in a background thread, i need to call it into the main thread

I hate to say this but doing privilege escalation correctly is not easy, and you will have to climb a steep learning curve. My advice here is that you work through EBAS. The sample has extensive documentation, both in the read me and in the comments. If you hit specific problems I’m happy to answer your questions about those, but I don’t think you’re going to find a “noob oriented guide”.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
Running privilaged tasks in pure swift app
 
 
Q