ioswebauthnfido

Passkey assertion signature verification fail on iOS


I'm trying to validate the signature received from an assertion request but fail. Here are the objects I've receiving from the client, the attestation and assertion, respectively:

const attestationChallenge = 'p58vEOB1TJ0Rh3PSwyTKnujxmYYQmGka20bYBK8hw8sfL/2btBbNeSmIiycTsvFfEgGzp7X5JVkpW4FMer6mcT5DOcgfh10OGXUdFe51exz/mOuj7+TRIxQNcaJ0jv/QTT9kCvTRydo2ntP2gsf2oVnVH+nweTEG4iY9GNFwDcU=';

const attestation = {
    id: 'XT39NMONrxoh29HHmyh35qwaS1o=',
    rawId: 'XT39NMONrxoh29HHmyh35qwaS1o=',
    response: {
        clientDataJSON: 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoicDU4dkVPQjFUSjBSaDNQU3d5VEtudWp4bVlZUW1Ha2EyMGJZQks4aHc4c2ZMXzJidEJiTmVTbUlpeWNUc3ZGZkVnR3pwN1g1SlZrcFc0Rk1lcjZtY1Q1RE9jZ2ZoMTBPR1hVZEZlNTFleHpfbU91ajctVFJJeFFOY2FKMGp2X1FUVDlrQ3ZUUnlkbzJudFAyZ3NmMm9WblZILW53ZVRFRzRpWTlHTkZ3RGNVIiwib3JpZ2luIjoiaHR0cHM6Ly90bXB0ZXN0NTU1LmJsb2Nrc29mdGxhYi5jb20ifQ==',
        attestationObject: 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViY85atP17k4jCSodKGG3KumgFtv9mzmvanMjekolX24mVdAAAAAPv8MAcVTk7MjAtuAgVX170AFF09/TTDja8aIdvRx5sod+asGktapQECAyYgASFYIHSvWI6jrp0v03xk2GzDDnMFD3hkZ+Vu4IFvXLPBNUDLIlgg8E4TIs8fiErLT7nEeOI6gjb+zTsKSQjPi6bplGD4djA='
    }
};

const assertionChallenge = 'ECJ3xtuY4U71MEZyjRa8pKcFBPXkRORXkzNe43P-OpdITVirSPjpn9aTzfFUOM0Gd9yR-KI82RRkK0_nei-2zy0HXVSDvfQXwLsIZmXBY7uuwSEaDEB5G57fc3B3Uu1bv8tjLSsdpzp5BNSi5kWNIcImwTVmAyeeNXuKp8ZPhbs';

const assertion = {
    id: 'ibYKqMzdLS3eEYNZKzoYBuPHNlY=',
    rawId: 'ibYKqMzdLS3eEYNZKzoYBuPHNlY=',
    response: {
        clientDataJSON: 'eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiRUNKM3h0dVk0VTcxTUVaeWpSYThwS2NGQlBYa1JPUlhrek5lNDNQLU9wZElUVmlyU1BqcG45YVR6ZkZVT00wR2Q5eVItS0k4MlJSa0swX25laS0yenkwSFhWU0R2ZlFYd0xzSVptWEJZN3V1d1NFYURFQjVHNTdmYzNCM1V1MWJ2OHRqTFNzZHB6cDVCTlNpNWtXTkljSW13VFZtQXllZU5YdUtwOFpQaGJzIiwib3JpZ2luIjoiaHR0cHM6Ly90bXB0ZXN0NTU1LmJsb2Nrc29mdGxhYi5jb20ifQ==',
        authenticatorData: '85atP17k4jCSodKGG3KumgFtv9mzmvanMjekolX24mUdAAAAAA==',
        signature: 'MEUCIQCAJrsIKKXp7Fmw8jVzxH5eLVTZqSpx0OihDtGCbZ5/ywIgYH48xfQlAA6+lY0NjWsHa7+vwEzmGwEOU132zwAOYTo=',
        userHandle: 'SmU2Qm9NaDM3dW5sQVFUNU5FVGllZw=='
    }
};

Here's the decoded JWK of the public key:

{
  "kty": "EC",
  "alg": "ES256",
  "crv": "P-256",
  "x": "dK9YjqOunS/TfGTYbMMOcwUPeGRn5W7ggW9cs8E1QMs=",
  "y": "8E4TIs8fiErLT7nEeOI6gjb+zTsKSQjPi6bplGD4djA="
}

I've also noticed a pattern in the signatures: they all seem to begin with a very similar structure, usually it begins with ME and contains characters such as U, I, C and Q shortly after, but not as consistently as ME. This leads me to believe the signature begins with some stable hash of the authenticator data, as written per in spec, however, it only applies to Apple attestation format, but the format returned from the attestation request is none and trying to concatenate the challenge and the authenticator data doesn't seem to make the verification work.

Here's what I've tried:

const authData = Buffer.from(assertion.response.authenticatorData, 'base64');

const { createHash, verify } = require('crypto');

const hash = createHash('sha256').update(Buffer.from(attestation.response.clientDataJSON, 'base64')).digest();

const concat = Buffer.concat([authData, hash]);

const result = verify(
  null,
  concat,
  { key, format: 'jwk' }, // the JWK above
  Buffer.from(assertion.response.signature, 'base64')
);

console.log(result);

Solution

  • On the attestation part, there is nothing to validate. If we parse it we get

    {
      attStmt: {},
      authData: <Binary Data>,
      fmt: 'none'
    }
    

    And as the fmt is none, you have nothing to validate this part. Now this isn't always important, the validation here would validate that the authenticator is authentically of the type it says.

    For the second part, you are right, with the data you have there it wont validate the signature. Now your code at glance seems correct, but if you check the ID of the key you generated, XT39NMONrxoh29HHmyh35qwaS1o=, it doesn't match the key used in the assertion, ibYKqMzdLS3eEYNZKzoYBuPHNlY=. That means a different private key than the one the public key matches has been used for the signature.

    You would either need to set so it uses the key you have above on the assertion, or you'd need use the public key that matches the one used in the assertion.