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.
- Apple Team ID : EQHXZ8M8AV
- Bundle ID starting with com.apple.
- 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?
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"