I am trying to implement a login page in SwiftUI for an idp that relies on passkeys only, following the sample code from the food truck app.
The registration of a new passkey works fine but when it comes to signing in, ASAuthorizationPlatformPublicKeyCredentialProvider().createCredentialAssertionRequest returns a signature that cannot be verified by the server.
On safari (and other browsers) the signing in&up process works fine and additionally, a passkey registered from the swift app works on the web, which leads me to believe there is an issue in the AuthenticationServices framework as every other steps works without any problem.
The verification of the signature happens on the server side (after several validation steps of the other parameters) with WebCrypto.subtle.verify(verifyAlgorithm, key, signature, data);
With the data argument being a concat of the clientDataJSON and the authenticatorData and for an apple authenticator, the key argument (which is the public key stored by the server) is an EC2 key with the following verifyAlgorithm argument:
verifyAlgorithm = {
name: 'ECDSA',
hash: { name: SHA-256 },
};
After carefully analyzing multiple responses, coming both from the app and safari, either on iOS or macOS, I can safely say that the ASAuthorizationResult.passkeyAssertion returns the expected values for:
- rawAuthenticatorData
- rawClientDataJSON
- credentialID
- userID
Which all match the expected values during the server-side validation. The only remaining value from the ASAuthorizationResult.passkeyAssertion is the signature, which as mentioned above, is invalid when verified by the server.
I already submitted a bug report (FB15113372) as well as a DTS request, but haven’t received any feedback yet.
In order to further narrow down the problem, I replicated the signature verification process in a sage notebook. I got the same result: the signature produced in Safari is fine, but the one from the Swift app is invalid. I collected some thoughts of potential issues in this notebook, but I still haven’t been able to draw a clear conclusion on why does this issue occur.
Hence if anyone has knowledge of this issue or has a similar problem with signature verification, their advice is most welcomed.
Thank you in advance for your help
PS: All the recent tests were made on the latest publicly available OS releases (iOS 18.01, macOS 15.0.1) and Xcode 16.0
I realize that I have actually made a big mistake. When receiving the challenge on the iOS client, I forgot to Base64 decode it, which led ASAuthorizationPlatformPublicKeyCredentialProvider.createCredentialAssertionRequest() to Base64 encode it a second time.
When receiving the rawClientDataJSON from the ASAuthorizationResult, in order to send the appropriate challenge back to the server, I was Base64 decoding it from the rawClientDataJSON to then send it in the expected format (removed the second Base64 encoding) to the server.
This was a trick I used for the .passkeyRegistration case (as I also forgot to Base64 decode the challenge in the first place there as well), which happened to work as there is no signature produced from the clientDataHash, hence just decoding the challenge afterwards was enough. But for the .passkeyAssertion case, as the signature is produced from the clientDataHash, this obviously doesn't work anymore. When finding this previous "solution", I thought this was just some Base64/Base64URL conversion mistake, but it was actually much more concerning and it led me straight into this error.
I guess what could have saved me from this mistake is if both createCredentialAssertionRequest() and createCredentialRegistrationRequest() were doing a type check on the challenge received, to make sure it is of type BufferSource (and not Base64URL encoded) as both navigator.credentials.get() and .create() do and return a type error in case of such mistake.
My apologies for this inaccuracy I posted here. I hope this can still help if someone ever runs into the same issue as I did.