Launch constraints using LightweightCodeRequirements framework

MacOS Version: 14.7.2 macOS SDKs: macOS 14.5 -sdk macosx14.5

I am working on a sample program for validation Against:

  • Team Identifier
  • Developer ID

I started with validating Team Identifier, but my validation is not working and it is allowing to launch programs which are not matching the team identifier in the signature.

Below is my code:

func verifyExecutableWithLCR(executablePath: String, arguments: [String]) -> Bool {
    let task = Process()
    task.launchPath = executablePath
    task.arguments = arguments

    if #available(macOS 14.4, *) {
        print("launchRequirementData is available on this system.")

        do {
            let req = try OnDiskCodeRequirement.allOf {
                TeamIdentifier("ABCDEFGHI")
                //SigningIdentifier("com.***.client.***-Client.****")
            }
            
            let encoder = PropertyListEncoder()
            encoder.outputFormat = .xml
            let requirementData = try encoder.encode(req)
            
            task.launchRequirementData = requirementData
            print("launchRequirementData is set.")

            try task.run()
            print("[SUCCESS] Executable passed the code signature verification.")
            return true
        } catch {
            print("[ERROR] Code signature verification failed: \(error.localizedDescription)")
            return false
        }
    } else {
        print("[WARNING] launchRequirement is not available on this macOS version.")
        return false
    }
}

Could you please help me in identifying whay am I doing wrong here?

Answered by DTS Engineer in 823593022

Hmmm, this is working for me. I’ve included the source for my test tool at the end of this post. If I comment out the line that sets launchRequirementData, I see this:

will start
did start
did finish, status: 0

If I leave it in place I see this:

will start
did start
did finish, status: 9

That 9 is SIGKILL, and the crash report shows that the trusted execution system killed the process before it executed any instructions.

I’m testing with Xcode 16.2 on macOS 15.2. I don’t have time to test with macOS 14 today. However, I figured you could run my code and see what you get.

Share and Enjoy

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

import Foundation
import LightweightCodeRequirements

func signedByMyTeamRequirement() throws -> Data {
    let req = try OnDiskCodeRequirement.allOf {
        TeamIdentifier("SKMME9E2Y8")
    }
    let encoder = PropertyListEncoder()
    encoder.outputFormat = .xml
    return try encoder.encode(req)
}

func main() {
    do {
        print("will start")
        let p = Process()
        p.executableURL = URL(fileURLWithPath: "/usr/bin/true")
        p.launchRequirementData = try signedByMyTeamRequirement()
        try p.run()
        print("did start")
        p.waitUntilExit()
        print("did finish, status: \(p.terminationStatus)")
    } catch {
        print("did not start, error: \(error)")
    }
}

main()
Accepted Answer

Hmmm, this is working for me. I’ve included the source for my test tool at the end of this post. If I comment out the line that sets launchRequirementData, I see this:

will start
did start
did finish, status: 0

If I leave it in place I see this:

will start
did start
did finish, status: 9

That 9 is SIGKILL, and the crash report shows that the trusted execution system killed the process before it executed any instructions.

I’m testing with Xcode 16.2 on macOS 15.2. I don’t have time to test with macOS 14 today. However, I figured you could run my code and see what you get.

Share and Enjoy

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

import Foundation
import LightweightCodeRequirements

func signedByMyTeamRequirement() throws -> Data {
    let req = try OnDiskCodeRequirement.allOf {
        TeamIdentifier("SKMME9E2Y8")
    }
    let encoder = PropertyListEncoder()
    encoder.outputFormat = .xml
    return try encoder.encode(req)
}

func main() {
    do {
        print("will start")
        let p = Process()
        p.executableURL = URL(fileURLWithPath: "/usr/bin/true")
        p.launchRequirementData = try signedByMyTeamRequirement()
        try p.run()
        print("did start")
        p.waitUntilExit()
        print("did finish, status: \(p.terminationStatus)")
    } catch {
        print("did not start, error: \(error)")
    }
}

main()

Hi Quinn, Thanks for sharing the result from your setup. Below is the output from my Mac machine:

macOS Version: 14.7.2
SDK Version: 14.5
will start
launchRequirementData is set (486 bytes)
Decoded launchRequirementData:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>value</key>
	<dict>
		<key>arrayKey</key>
		<string>$and-array</string>
		<key>key</key>
		<string>$and</string>
		<key>value</key>
		<array>
			<dict>
				<key>key</key>
				<string>team-identifier</string>
				<key>value</key>
				<string>SKMME9E2Y8</string>
			</dict>
		</array>
	</dict>
</dict>
</plist>

did start
did finish, status: 0
Program ended with exit code: 0


Does this mean LightweightCodeRequirements framework is not working as expected on all setups? Can this be a bug in the framework?

Also noticed that on Macos 15.3 it works fine.. Is there any known issue?


import Foundation
import LightweightCodeRequirements

func getMacOSVersion() -> String {
    let process = Process()
    let pipe = Pipe()
    
    process.executableURL = URL(fileURLWithPath: "/usr/bin/sw_vers")
    process.arguments = ["-productVersion"]
    process.standardOutput = pipe
    
    do {
        try process.run()
        process.waitUntilExit()
        
        let data = pipe.fileHandleForReading.readDataToEndOfFile()
        if let version = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) {
            return version
        }
    } catch {
        return "Unknown"
    }
    
    return "Unknown"
}

func getSDKVersion() -> String {
    let process = Process()
    let pipe = Pipe()
    
    process.executableURL = URL(fileURLWithPath: "/usr/bin/xcrun")
    process.arguments = ["--sdk", "macosx", "--show-sdk-version"]
    process.standardOutput = pipe
    
    do {
        try process.run()
        process.waitUntilExit()
        
        let data = pipe.fileHandleForReading.readDataToEndOfFile()
        if let version = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) {
            return version
        }
    } catch {
        return "Unknown"
    }
    
    return "Unknown"
}

func signedByMyTeamRequirement() throws -> Data {
    let req = try OnDiskCodeRequirement.allOf {
        TeamIdentifier("SKMME9E2Y8")
    }
    let encoder = PropertyListEncoder()
    encoder.outputFormat = .xml
    return try encoder.encode(req)
}

func printLaunchRequirementData(req: Data) {
    print("launchRequirementData is set (\(req.count) bytes)")

    if let plistObject = try? PropertyListSerialization.propertyList(from: req, options: [], format: nil),
       let plistData = try? PropertyListSerialization.data(fromPropertyList: plistObject, format: .xml, options: 0),
       let plistString = String(data: plistData, encoding: .utf8) {
        print("Decoded launchRequirementData:\n\(plistString)")
    } else {
        print("[ERROR] Failed to decode launchRequirementData.")
    }
}

func main() {
    let macOSVersion = getMacOSVersion()
    let sdkVersion = getSDKVersion()
    
    print("macOS Version: \(macOSVersion)")
    print("SDK Version: \(sdkVersion)")
    
    do {
        print("will start")
        let p = Process()
        p.executableURL = URL(fileURLWithPath: "/usr/bin/true")
        p.launchRequirementData = try signedByMyTeamRequirement()
        
        if let launchData = p.launchRequirementData {
            printLaunchRequirementData(req: launchData)
        } else {
            print("[ERROR] launchRequirementData is nil.")
        }
        
        try p.run()
        print("did start")
        p.waitUntilExit()
        print("did finish, status: \(p.terminationStatus)")
    } catch {
        print("did not start, error: \(error)")
    }
}

main()

Written by macnd in 823654022
Also noticed that on Macos 15.3 it works fine. Is there any known issue?

Well, there is now (-:

Unfortunately I’ve been unable to track down the origin of the change you’re seeing between 14.7.2 and 15.3 (well, 15.2 for me). My advice is that you retest this on 15.0; it’s likely that that’s the inflection point. Presuming that things work there, that’d be where you can start relying on this feature.

Share and Enjoy

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

Hi Quinn, I upgrade my MacOS to 15.2 and still the Launch constrains are not working on my setup. If you suggest I will file a bug so that it can be looked by the concern Apple team. I checked on few other setups and it is working fine including on MacOS 14.7.1.

I need another help, is there a way we can put Launch constrains based on Authority, eg: Authority=Developer ID Application: Abc LLC (EQHXZ8M8AV)

Thanks

The Authority fields displayed by codesign are simply a summary of the subject of each certificate in the chain. If you want to check that, check the certificate fields.

However, you’re much better off requiring a ValidationCategory of .developerID and a specific Team ID.

Share and Enjoy

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

Thanks Quinn for all the support, I am able to make full use of the LCR. I noticed the LCR API is only available in swift, is there a way to use it in my C/Cpp project without Obj-C bridging? or any different method (eg in endpoint security) which can alternatively be used directly in cpp?

Written by macnd in 824490022
is there a way to use it in my C/Cpp project without Obj-C bridging?

No. Well, not the LightweightCodeRequirements framework specifically. Some related functionality is exposed via static properties — see the first two articles listed here — but that’s solving a different problem.

I expect this trend towards Swift-only APIs to continue. If you prefer to code in C-based languages, it’s important to become comfortable with bridging technologies.

Apropos that, Swift’s recently introduce C++ interoperability means that you don’t necessarily have to bounce through Objective-C[++].

Share and Enjoy

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

Launch constraints using LightweightCodeRequirements framework
 
 
Q