MasterKale / SimpleWebAuthn

WebAuthn, Simplified. A collection of TypeScript-first libraries for simpler WebAuthn integration. Supports modern browsers, Node, Deno, and more.
https://simplewebauthn.dev
MIT License
1.62k stars 137 forks source link

Invalid passkey when authenticating from native iOS passkey data #482

Closed edwinius closed 10 months ago

edwinius commented 1 year ago

Describe the issue

We are trying to implement passkey with our native iOS code using Swift which we have followed from the official Apple Developer https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys/ . However it seems that we always get invalid passkey even though the body seems alright.

Reproduction Steps

Using Swift native ASAuthorization

let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: domain)
let registrationRequest = publicKeyCredentialProvider.createCredentialRegistrationRequest(challenge: challenge, name: userName, userID: userID)

let authController = ASAuthorizationController(authorizationRequests: [ registrationRequest ] )
authController.delegate = self
authController.presentationContextProvider = self
authController.performRequests()

Expected behavior

Code Samples + WebAuthn Options and Responses

Here is the registrationData:

{
    "authenticatorAttachment" : "platform",
    "id" : "FOTjVppiM_OvbuiwG4iZBRq0hsg",
    "rawId" : "FOTjVppiM_OvbuiwG4iZBRq0hsg",
    "response" : {
      "attestationObject" : "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViY7XiWdYBFaxUTCwPV3yKCwKoVd7sM9REl2ZnaTvsBZZFdAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBTk41aaYjPzr27osBuImQUatIbIpQECAyYgASFYIA7tS16_DCWmpc7DjOt3k_RGVQHl5gmDKf8e92mdShX8IlggBSq_P0ql6TiQAjSOR5hNtOJdRyHl9uwCtUlmssaYIEs",
      "clientDataJSON" : "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwib3JpZ2luIjoiaHR0cDpcL1wvZGV2LmhhdmVud2FsbGV0LmlvIiwiY2hhbGxlbmdlIjoiQXFyMUk4UGdPa012ekdiZE82alBsd0NlX19aemhkV243MUtfeDNUZGdVMCJ9"
    },
    "type" : "public-key",
    "clientExtensionResults" : {

    }
  }

And this is the authenticate user data:

{
  "id": "FOTjVppiM_OvbuiwG4iZBRq0hsg",
  "clientExtensionResults": {

  },
  "response": {
    "userHandle": "NDkxNGFjY2YtMzFmYy00ZjBmLTlmYTktMDhlZTgxYWU4MDE2",
    "clientDataJSON": "eyJvcmlnaW4iOiJodHRwOlwvXC9kZXYuaGF2ZW53YWxsZXQuaW8iLCJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiSlNJVnJ2QjVObG1keWNUWkhRZ2puMDdSUnlvOHBGY1VvOHNsRDdQYy1tVSJ9",
    "signature": "MEYCIQDS7IZCmvyBwORAAllz1mUsjgHQONxuzvhUl1h-ot1IfAIhAKvN_wlDJt0gpqSfBdbFsqR88tSXviY2jlMLFiquLnP7",
    "authenticatorData": "7XiWdYBFaxUTCwPV3yKCwKoVd7sM9REl2ZnaTvsBZZEdAAAAAA"
  },
  "rawId": "FOTjVppiM_OvbuiwG4iZBRq0hsg",
  "authenticatorAttachment": "platform",
  "type": "public-key"
}

rawId and everything seems fine, but still always get invalid passkey

Dependencies

Everything works fine using browser and Android device. However from native iOS device, even though successfully registered passkey, couldn't get it to authorize the user.

MasterKale commented 10 months ago

Hello @edwinius, sorry for the delay. I wrote a quick script to confirm your issue:

const {
  verifyAuthenticationResponse,
  verifyRegistrationResponse
} = require('@simplewebauthn/server');
const {
  decodeAttestationObject,
  parseAuthenticatorData,
  isoBase64URL,
} = require('@simplewebauthn/server/helpers');

const iOSRegResponse = {
  id: 'FOTjVppiM_OvbuiwG4iZBRq0hsg',
  rawId: 'FOTjVppiM_OvbuiwG4iZBRq0hsg',
  response: {
    attestationObject:
      'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViY7XiWdYBFaxUTCwPV3yKCwKoVd7sM9REl2ZnaTvsBZZFdAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBTk41aaYjPzr27osBuImQUatIbIpQECAyYgASFYIA7tS16_DCWmpc7DjOt3k_RGVQHl5gmDKf8e92mdShX8IlggBSq_P0ql6TiQAjSOR5hNtOJdRyHl9uwCtUlmssaYIEs',
    clientDataJSON:
      'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwib3JpZ2luIjoiaHR0cDpcL1wvZGV2LmhhdmVud2FsbGV0LmlvIiwiY2hhbGxlbmdlIjoiQXFyMUk4UGdPa012ekdiZE82alBsd0NlX19aemhkV243MUtfeDNUZGdVMCJ9',
    authenticatorAttachment: 'platform',
  },
  type: 'public-key',
  clientExtensionResults: {},
};

const iOSAuthResponse = {
  id: 'FOTjVppiM_OvbuiwG4iZBRq0hsg',
  clientExtensionResults: {},
  response: {
    userHandle: 'NDkxNGFjY2YtMzFmYy00ZjBmLTlmYTktMDhlZTgxYWU4MDE2',
    clientDataJSON:
      'eyJvcmlnaW4iOiJodHRwOlwvXC9kZXYuaGF2ZW53YWxsZXQuaW8iLCJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiSlNJVnJ2QjVObG1keWNUWkhRZ2puMDdSUnlvOHBGY1VvOHNsRDdQYy1tVSJ9',
    signature:
      'MEYCIQDS7IZCmvyBwORAAllz1mUsjgHQONxuzvhUl1h-ot1IfAIhAKvN_wlDJt0gpqSfBdbFsqR88tSXviY2jlMLFiquLnP7',
    authenticatorData: '7XiWdYBFaxUTCwPV3yKCwKoVd7sM9REl2ZnaTvsBZZEdAAAAAA',
  },
  rawId: 'FOTjVppiM_OvbuiwG4iZBRq0hsg',
  authenticatorAttachment: 'platform',
  type: 'public-key',
};

(async () => {
  /**
   * Registration
   */
  const regVerification = await verifyRegistrationResponse({
    expectedChallenge: 'Aqr1I8PgOkMvzGbdO6jPlwCe__ZzhdWn71K_x3TdgU0',
    expectedOrigin: 'http://dev.havenwallet.io',
    response: iOSRegResponse,
  });

  console.log('reg verified:', regVerification.verified); // true

  /**
   * Authentication
   */
  // Pull values out of registration
  const attestationObjectBuffer = isoBase64URL.toBuffer(iOSRegResponse.response.attestationObject);
  const decodedAttestationObject = decodeAttestationObject(attestationObjectBuffer);
  const authData = decodedAttestationObject.get('authData');
  const parsedAuthData = parseAuthenticatorData(authData);

  const authVerification = await verifyAuthenticationResponse({
    expectedChallenge: 'JSIVrvB5NlmdycTZHQgjn07RRyo8pFcUo8slD7Pc-mU',
    expectedOrigin: 'http://dev.havenwallet.io',
    expectedRPID: 'dev.havenwallet.io',
    response: iOSAuthResponse,
    authenticator: {
      counter: 0,
      credentialID: parsedAuthData.credentialID,
      credentialPublicKey: parsedAuthData.credentialPublicKey,
    },
  });

  console.log('auth verified:', authVerification.verified); // false
})();

Everything works fine using browser and Android device. However from native iOS device, even though successfully registered passkey, couldn't get it to authorize the user.

At this point the fact that nothing about the response raises any other errors, and the signature at the very end can't be verified, and the fact that Android native APIs are fine with the same server-side verification logic, then I'm suspecting this is an iOS issue.

What specific version of iOS 16 are you running? I'm asking around my network to see if anyone else has experienced this before and might have a clue as to what's the culprit.

MasterKale commented 10 months ago

I'd also recommend updating to the latest iOS 17 if you can and see if the issue's been fixed.