XPC connection trust

I have an XPC connection that I want to know if I should trust using the following code. Does this look like a workable secure approach?

func connectionIsValid(connection: NSXPCConnection) -> Bool {

 let checker = CodesignChecker()
 var localCertificates: [SecCertificate] = []
 var remoteCertificates: [SecCertificate] = []
 let pid = connection.processIdentifier

 do {
  localCertificates = try checker.getCertificatesSelf()
  remoteCertificates = try checker.getCertificates(forPID: pid)
 } catch let error as CodesignCheckerError {
  NSLog(CodesignCheckerError.handle(error: error))
 } catch let error {
  NSLog("Something unexpected happened: \(error.localizedDescription)")
 }

 NSLog("Local certificates: \(localCertificates)")
 NSLog("Remote certificates: \(remoteCertificates)")

 let remoteApp = NSRunningApplication.init(processIdentifier: pid)

 if remoteApp != nil && !remoteCertificates.isEmpty {

  let policy = SecPolicyCreateBasicX509()

  var optionalTrust: SecTrust?
  let status = SecTrustCreateWithCertificates(remoteCertificates as AnyObject,
                        policy,
                        &optionalTrust)
  guard status == errSecSuccess else {
   NSLog("failed evaluating trust")
   return false
  }

  let trust = optionalTrust!
  var secResult = SecTrustResultType.invalid
  SecTrustGetTrustResult(trust, &secResult)
  if(secResult == .proceed || secResult == .unspecified) {
   let names = remoteCertificates.map { commonName(cert:$0) }
   let validCert1 = ["Apple Development: john.doe(at)example.com (XY12XY12X)", "Apple Worldwide Developer Relations Certification Authority", "Apple Root CA"]

   if(names == validCert1) {
    NSLog("Found a valid client (fingerprint #1)")
    return true
   }
   let validCert2 = ["Developer ID Application: John Doe (XY13XY13X)", "Developer ID Certification Authority", "Apple Root CA"]
   if(names == validCert2) {
    NSLog("Found a valid client (fingerprint #2)")
    return true
   }
   return false
  } else {
   NSLog("Got invalid secResult: \(secResult.rawValue)")
  }
  return false
 }

func commonName(cert: SecCertificate) -> String {
 var commonName: CFString?
 SecCertificateCopyCommonName(cert, &commonName)
 return commonName as String? ?? ""
}

Replies

Does this look like a workable secure approach?

When it comes to securing an XPC connection, the only approach that I’m fully happy with is the brand new xpc_connection_set_peer_code_signing_requirement routine. See this post, and the other posts on that thread, for a lot more context.

Share and Enjoy

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

This looks great but how do I use it from Swift (OSX only)?

As you can see I use NSXPCConnection right now. I could switch the entire XPC codebase to ObjC for this but that is a drawback for sure.

Anyways my reasoning was: If I use SecTrustCreateWithCertificates to validate the chain and the chain is OK, then I can query the certificates involved a bit more loosely and just check CN like above.

One problem I want to solve is that I have a helper tool I talk to over XPC and sometimes it is the Developer ID signed version of this certificate and sometimes it is the notarised variant so two flavors of credentials are OK for me to trust. Otherwise I was using the approach from https://github.com/suolapeikko/PrivilegedTaskRunner/blob/master/PrivilegedTaskRunnerHelper/CodesignChecker.swift which relies on the fact that the certificate chain of both helper tool and connecting app need to be identical which at least to a layperson like me seems sound but is inflexible wrt the double-signing issue. This double-signing thing will go away when I develop the app less frequently and users only run notarised versions.

This looks great but how do I use it from Swift … ?

You can call the XPC C API from Swift, although doing that is not much fun. What you want is Foundation equivalent of xpc_connection_set_peer_code_signing_requirement but, as I mentioned in that other thread, that’s not available right now.

Anyways my reasoning was: If I use SecTrustCreateWithCertificates

Definitely don’t do you own trust evaluation. You can do the equivalent using a code signing requirement and xpc_connection_set_peer_code_signing_requirement makes it clear that this is the direction we’re heading.

One problem I want to solve is that I have a helper tool I talk to over XPC and sometimes it is the Developer ID signed version of this certificate and sometimes it is the notarised variant so two flavors of credentials are OK for me to trust.

The idea here would be to craft two different code signing requirements:

  • For the debug version of your listener, accept both Apple Development and Developer ID signed clients.

  • For the release version of your listener, only accept Developer ID signed clients.

The code signing requirements language is quite capable of handling that.

Share and Enjoy

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

The code signing requirements language is quite capable of handling that.

Oh, and if you need specific advice on how to craft those requirements, I’m going to ask you to open a DTS tech support incident so that I can research that properly. Code signing requirements are quite tricky, and I’m not going to give you a definitive answer on that front without having it reviewed properly, and a TSI will allow me to allocate the time to do that.

Share and Enjoy

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