Passkeys: rawAttestationObject doesn't contain public key

I'm trying to implement passkeys in my app. I successfully get to the dialog in iOS simulator to register with a Passkey and I can also read the result and see all the right things in credentialRegistration.rawClientDataJSON. The one thing that's not working is when decoding the rawAttestationObject (which should be CBOR as I understand), I find all data defined in the spec (aaguid, credentialIdLength, credentialId) except for the credentialPublicKey! The rawAttestationObject basically ends after the credentialId. I see this both when decoding the rawAttestationObject manually as well as when using WebAuthn libraries on the server, which will give me an "Unexpected end of CBOR data" error.

Any ideas why the rawAttestationObject does not contain the public key?

For reference, here is the initialization of the Passkey request:

let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: options.domain)

let registrationRequest = publicKeyCredentialProvider.createCredentialRegistrationRequest(challenge: challenge, name: name, userID: userID)

let authController = ASAuthorizationController(authorizationRequests: [ registrationRequest ])

authController.performRequests()

And here is how I handle the result:

case let credentialRegistration as ASAuthorizationPlatformPublicKeyCredentialRegistration:
  let rawAttestationObject = credentialRegistration.rawAttestationObject!.base64EncodedString()
  let credentialID = credentialRegistration.credentialID.base64EncodedString()
  let rawClientDataJSON = credentialRegistration.rawClientDataJSON.base64EncodedString()
  let response: PasskeysResponse = [
    "attestationObject": rawAttestationObject,
    "credentialId": credentialID,
    "clientDataJson": rawClientDataJSON,
  ]

Here is an example for a decoded attestation object:

{
  "rpIdHash": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYmW4=",
  "flags": {
    "userPresent": false,
    "userVerified": false,
    "backupEligibility": true,
    "backupState": true,
    "attestedCredentialData": true,
    "extensionData": false
  },
  "signCount": 425116148,
  "aaguid": "20318e2d-77fa-f54d-bed7-ba15ccd3fade",
  "credentialId": "1B1KJf6uYF0AAAAAAAAAAAAAAAAAAAAAAAAAAAAUQW65BAqkeKqu97vbc0Se5R1F3Y+lAQIDJiABIVggtdSX2ZAHsBxU4ja1xP6hCZGUXgUCb6Ipau3stU8rrz4iWCBwhOBWOgwT4yKRnU1hA11thC8+CvjmrCkfq//648cwHg==",
  "credentialPublicKey": ""
}

As you can see, it looks all good except for the "credentialPublicKey": "" part.

Answered by Systems Engineer in 762455022

You appear to be attempting to decode rawAttestationObject as an authenticator data rather than as just an attestation object. The authenticator data is a different data structure that (sometimes) contains an attestation object as one of its fields. All the data you need is in your above response, just decoded as the wrong type :)

For reference, the COSE-encoded public key from the object you shared (when correctly decoded) is:

{
  1: 2,  # Key type. `2` is EC2, or "elliptic curve key with x and y coordinates"
  3: -7, # Key algorithm. `-7` is ES256.
  -1: 1, # Elliptic curve. `1` is P-256.
  -2: h'B5D497D99007B01C54E236B5C4FEA10991945E05026FA2296AEDECB54F2BAF3E', # x coordinate
  -3: h'7084E0563A0C13E322919D4D61035D6D842F3E0AF8E6AC291FABFFFAE3C7301E'  # y coordinate
}
Accepted Answer

You appear to be attempting to decode rawAttestationObject as an authenticator data rather than as just an attestation object. The authenticator data is a different data structure that (sometimes) contains an attestation object as one of its fields. All the data you need is in your above response, just decoded as the wrong type :)

For reference, the COSE-encoded public key from the object you shared (when correctly decoded) is:

{
  1: 2,  # Key type. `2` is EC2, or "elliptic curve key with x and y coordinates"
  3: -7, # Key algorithm. `-7` is ES256.
  -1: 1, # Elliptic curve. `1` is P-256.
  -2: h'B5D497D99007B01C54E236B5C4FEA10991945E05026FA2296AEDECB54F2BAF3E', # x coordinate
  -3: h'7084E0563A0C13E322919D4D61035D6D842F3E0AF8E6AC291FABFFFAE3C7301E'  # y coordinate
}

Dear Garrett, based on all the threads on passkeys I read in this forum before asking my question, I was hoping to get a rely from you. And sure enough, you delivered. Thanks to your answer I got it working now!

If you don't mind, I'd have a follow-up question now that it works: I'm wondering about the role of the challenge in registration. The way I see it, there is no signature returned when using ASAuthorizationPlatformPublicKeyCredentialRegistration. Why does createCredentialRegistrationRequest take a challenge as a param then? All it seems to do is return the same challenge. So when sending the above mentioned attestation object to the server, it seems there is no way to verify that the public key I'm sending was indeed intended for the session which requested the challenge from the server earlier. Or am I missing something here?

Alright, got it. Again, thanks for your help, I really appreciate it!

Passkeys: rawAttestationObject doesn't contain public key
 
 
Q