Protecting XPC service when called from Authorisation Plugin

I have Authorisation Plugin which talks using XPC to my Launch Daemon to perform privileged actions.

I want to protect my XPC service narrowing it to be called from known trusted clients.

Now since I want authorisation plugin code which is from apple to call my service, I cannot use my own team id or app group here.

I am currently banking on following properties of client connection.

  1. Apple Team ID : EQHXZ8M8AV
  2. Bundle ID starting with com.apple.
  3. Client signature verified By Apple.

This is what I have come up with.

func isClientTrusted(connection: NSXPCConnection) -> Bool {
    
    let clientPID = connection.processIdentifier
    logInfo("🔍 Checking XPC Client - PID: \(clientPID)")
    
    var secCode: SecCode?
    var secStaticCode: SecStaticCode?

    let attributes = [kSecGuestAttributePid: clientPID] as NSDictionary
    let status = SecCodeCopyGuestWithAttributes(nil, attributes, [], &secCode)

    guard status == errSecSuccess, let code = secCode else {
        logInfo("Failed to get SecCode for PID \(clientPID)")
        return false
    }

    let staticStatus = SecCodeCopyStaticCode(code, [], &secStaticCode)
    guard staticStatus == errSecSuccess, let staticCode = secStaticCode else {
        logInfo("Failed to get SecStaticCode")
        return false
    }

    var signingInfo: CFDictionary?
    let signingStatus = SecCodeCopySigningInformation(staticCode, SecCSFlags(rawValue: kSecCSSigningInformation), &signingInfo)

    guard signingStatus == errSecSuccess, let info = signingInfo as? [String: Any] else {
        logInfo("Failed to retrieve signing info")
        return false
    }

    // Extract and Verify Team ID
    if let teamID = info["teamid"] as? String {
        logInfo("XPC Client Team ID: \(teamID)")

        if teamID != "EQHXZ8M8AV" { // Apple's official Team ID
            logInfo("Client is NOT signed by Apple")
            return false
        }
    } else {
        logInfo("Failed to retrieve Team ID")
        return false
    }

    // Verify Bundle ID Starts with "com.apple."
    if let bundleID = info["identifier"] as? String {
        logInfo("XPC Client Bundle ID: \(bundleID)")

        if !bundleID.hasPrefix("com.apple.") {
            logInfo("Client is NOT an Apple system process")
            return false
        }
    } else {
        logInfo("Failed to retrieve Bundle Identifier")
        return false
    }

    // Verify Apple Code Signature Trust
    var trustRequirement: SecRequirement?
    let trustStatus = SecRequirementCreateWithString("anchor apple" as CFString, [], &trustRequirement)

    guard trustStatus == errSecSuccess, let trust = trustRequirement else {
        logInfo("Failed to create trust requirement")
        return false
    }

    let verifyStatus = SecStaticCodeCheckValidity(staticCode, [], trust)

    if verifyStatus != errSecSuccess {
        logInfo("Client's signature is NOT trusted by Apple")
        return false
    }

    logInfo("Client is fully verified as Apple-trusted")
    return true
}

Q: Just wanted community feedback, is this correct approach?

Answered by DTS Engineer in 829930022

There isn’t an ideal way to do this.

The canonical mechanism to protect your XPC endpoint is to apply a code-signing requirement, as explained in Validating Signature Of XPC Process.

IMPORTANT See TN3127 Inside Code Signing: Requirements for an in-depth discussion of code-signing requirements.

This mechanism doesn’t work in your case because you can’t control the process that loads your authorisation plug-in. It can be loaded by a variety of different system processes. Moreover, that set of processes has changed in the past and it’s not hard to imagine it changing again in the future.

So, you have a security/reliability trade-off to make:

  • If you apply a tight requirement then it’s possible for it to fail in the future if Apple changes the identity of the program that loads your plug-in.

  • If you apply a loose requirement then you might end up allowing connections from contexts where you really shouldn’t be.

If I were in your shoes I’d probably thread this needle as follows:

  • Apply an anchor apple requirement to the listener, ensuring that only Apple code can connect to it.

  • Also check that the effectiveUserIdentifier is 0.

If you’re being attacked by Apple code running as root, you have bigger problems (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

There isn’t an ideal way to do this.

The canonical mechanism to protect your XPC endpoint is to apply a code-signing requirement, as explained in Validating Signature Of XPC Process.

IMPORTANT See TN3127 Inside Code Signing: Requirements for an in-depth discussion of code-signing requirements.

This mechanism doesn’t work in your case because you can’t control the process that loads your authorisation plug-in. It can be loaded by a variety of different system processes. Moreover, that set of processes has changed in the past and it’s not hard to imagine it changing again in the future.

So, you have a security/reliability trade-off to make:

  • If you apply a tight requirement then it’s possible for it to fail in the future if Apple changes the identity of the program that loads your plug-in.

  • If you apply a loose requirement then you might end up allowing connections from contexts where you really shouldn’t be.

If I were in your shoes I’d probably thread this needle as follows:

  • Apply an anchor apple requirement to the listener, ensuring that only Apple code can connect to it.

  • Also check that the effectiveUserIdentifier is 0.

If you’re being attacked by Apple code running as root, you have bigger problems (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks for the response, I'll look into it as advised.

Protecting XPC service when called from Authorisation Plugin
 
 
Q