AppAttest - Invalid appUUID

Hi!

I'm generating assertions using DCAppAttestService.shared.generateAssertion. It's running for almost one and a half years and has the following issue. Approx 6% of our users trying to generate assertions have issues and according to our analytics about half of them have invalid_input error during assertion process). I've managed to reproduce this issue on test device and noticed some weird scenario:

(first app run)

  • The AppAttest key generated and attested at Apple side successfully. Key Identifier persisted.
  • Attestation object verification on backend and public key extraction is ok
  • Unlimited number of assertion can be generated this time

(second app run)

  • Key Identifier persisted on previous app run is read and passed to DCAppAttestService.shared.generateAssertion
  • Invalid input error received.
  • Regeneration of key and attestation works fine.

So looks like there is a kinda state in assertion process - it works well after key generation on first run, but fails with invalid_input on second run.

As invalid_input error cannot say much about the issue, I've swizzled some methods of DCAppAttestService (https://developer.limneos.net/index.php?ios=15.2.1&framework=DeviceCheck.framework&header=DCAppAttestService.h) - _rewrapAsDCError, _loadAppUUID, _saveAppUUID. Swizzling implementation attached (swizzling.swift). As the swizzling logs show - when invalid_input raises, a strange error is printed (Error Domain=com.apple.appattest.error Code=-2 "Invalid appUUID" UserInfo={NSLocalizedDescription=Invalid appUUID}).

What can be the issue? In another app this behaviour isn't reproducible but they share similar dependency with App Attest - wrapping logic I've filed bug report no FB12205670. Thanks.

Logs:

ok case:

key generation:

swizzleLoadAppID 
swizzleSaveAppID EDA165DC-0781-4891-A16D-0979FC4FEB84
swizzleRewrap 

key attestation: (key_id: "XzTjW3V7944\/ljQ2C8LTpqug0t0gslVyhdWGUCnJXfY=")

swizzleLoadAppID EDA165DC-0781-4891-A16D-0979FC4FEB84
swizzleRewrap 

key assertion: (input key_id =  "XzTjW3V7944\/ljQ2C8LTpqug0t0gslVyhdWGUCnJXfY=" clientDataHash = "zUwl\/jiunewwd1ofhEOmgNGWM+oD7LmUGe6Te5Iv9pc=")

swizzleLoadAppID EDA165DC-0781-4891-A16D-0979FC4FEB84
swizzleRewrap 

issue case (DCError.invalid_input):

key assertion: (input key_id =  "XzTjW3V7944\/ljQ2C8LTpqug0t0gslVyhdWGUCnJXfY=" clientDataHash = "F8o5i+8PsZ5cTuyjlZoMe+kcbTG0\/R8Vw6tmjPlzlLc=")
swizzleLoadAppID 
swizzleRewrap Error Domain=com.apple.appattest.error Code=-2 "Invalid appUUID" UserInfo={NSLocalizedDescription=Invalid appUUID}

Swizzling logic:

    @objc func swizzleRewrap(obj: NSObject) -> NSObject {
        let returnValue = swizzleRewrap(obj: obj)
        print("swizzleRewrap \(obj)")
        return returnValue
    }

    @objc func swizzleLoadAppID() -> NSObject {
        let returnValue = swizzleLoadAppID()
        print("swizzleLoadAppID \(returnValue.debugDescription)")
        return returnValue
    }

    @objc func swizzleSaveAppID(app_id: NSObject) {
        swizzleSaveAppID(app_id: app_id)
        print("swizzleSaveAppID \(app_id)")
    }

    static func makeSwizzling() {
        let sel = NSSelectorFromString("_rewrapAsDCError:")
        DCAppAttestService.swizzleInstanceMethod(sel, #selector(DCAppAttestService.swizzleRewrap(obj:)))
        let sel1 = NSSelectorFromString("_loadAppUUID")
        DCAppAttestService.swizzleInstanceMethod(sel1, #selector(DCAppAttestService.swizzleLoadAppID))
        let sel2 = NSSelectorFromString("_saveAppUUID:")
        DCAppAttestService.swizzleInstanceMethod(sel2, #selector(DCAppAttestService.swizzleSaveAppID(app_id:)))
    }
}

public extension NSObjectProtocol {
    static func swizzleInstanceMethod(_ origin: Selector, _ replace: Selector) {
        let origin = class_getInstanceMethod(self, origin)
        let replace = class_getInstanceMethod(self, replace)

        if let origin = origin, let replace = replace {
            method_exchangeImplementations(origin, replace)
        }
    }
}