svgta1 / webauthn-php

GNU General Public License v3.0
2 stars 0 forks source link

Works fine most of the time, but fails for passkeys generated by Enpass - enpass.io #2

Open mathewparet opened 1 week ago

mathewparet commented 1 week ago

This library seems to work fine with all password managers, browsers and, and operating systems I've tried except for Enpass.

While attempting to register the passkey sent by Enpass an exception is thrown at vendor/svgta/webauthn/lib/register.php:118

Exception: Undefined property: stdClass::$publicKeyAlgorithm

The payload received from Enpass looks like this:

{
   "passkey":{
      "id":"qIzoqgJ8ZO3qrn6LQ9UttXGTAUvbWcsFM-gAhEedatY",
      "rawId":"qIzoqgJ8ZO3qrn6LQ9UttXGTAUvbWcsFM-gAhEedatY",
      "response":{
         "attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikrfib3PjzQH9ab0yDXhjFR6LN1Un-cXd32vJpl6_yc5tNAAAAAPOAlUB_FEnBqLOPgTsiVUEAIKiM6KoCfGTt6q5-i0PVLbVxkwFL21nLBTPoAIRHnWrWpQECAyYgASFYIARlkW_nc2GzO_S5C5Z5Bkh4CjugYg1dtmLhuNrybkmWIlggSjx51ABpkesJlQnGKM-qDodSrqrHziA5DO7l2Kt82Zg",
         "clientDataJSON":"eyJ0eXBlIjogIndlYmF1dGhuLmNyZWF0ZSIsICJjaGFsbGVuZ2UiOiAiNXQ5RTlJRFlOa1hrR0RfTExMdkJ3TXZ4Vk85V19PRjh5UkVKSEdyajdsZHJTRVVuZG5vc1JVaWFzYl80bHpiaV93Z3RpcmJySEJRMWthUkpKN29rWFEiLCAib3JpZ2luIjogImh0dHBzOi8vdGVzbGEtc3RhdHMucGlvbmVlcmR5bmFtaWNzLmNvbS5hdSJ9",
         "transports":[
            "internal",
            "hybrid"
         ]
      },
      "type":"public-key",
      "clientExtensionResults":[

      ],
      "authenticatorAttachment":"cross-platform"
   },
   "name":"Enpass"
}

Initially I thought it was an issue with Enpass, but they claim most websites are able to handle Enpass generated Passkeys.

svgta1 commented 6 days ago

It's seems that the data sent to the library is not good. The javascript must not give the payload given by passkey, but only the json in the passkey var :

{
      "id":"qIzoqgJ8ZO3qrn6LQ9UttXGTAUvbWcsFM-gAhEedatY",
      "rawId":"qIzoqgJ8ZO3qrn6LQ9UttXGTAUvbWcsFM-gAhEedatY",
      "response":{
         "attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikrfib3PjzQH9ab0yDXhjFR6LN1Un-cXd32vJpl6_yc5tNAAAAAPOAlUB_FEnBqLOPgTsiVUEAIKiM6KoCfGTt6q5-i0PVLbVxkwFL21nLBTPoAIRHnWrWpQECAyYgASFYIARlkW_nc2GzO_S5C5Z5Bkh4CjugYg1dtmLhuNrybkmWIlggSjx51ABpkesJlQnGKM-qDodSrqrHziA5DO7l2Kt82Zg",
         "clientDataJSON":"eyJ0eXBlIjogIndlYmF1dGhuLmNyZWF0ZSIsICJjaGFsbGVuZ2UiOiAiNXQ5RTlJRFlOa1hrR0RfTExMdkJ3TXZ4Vk85V19PRjh5UkVKSEdyajdsZHJTRVVuZG5vc1JVaWFzYl80bHpiaV93Z3RpcmJySEJRMWthUkpKN29rWFEiLCAib3JpZ2luIjogImh0dHBzOi8vdGVzbGEtc3RhdHMucGlvbmVlcmR5bmFtaWNzLmNvbS5hdSJ9",
         "transports":[
            "internal",
            "hybrid"
         ]
      },
      "type":"public-key",
      "clientExtensionResults":[

      ],
      "authenticatorAttachment":"cross-platform"
   }

You can look at this doc to see what it must be sent to the lib.

But, in the json data, I don't see publicKeyAlgorithm and publicKey. It's a problem. Without the alg and the publickey, it's not possible to verify the signature for authentication.

mathewparet commented 3 days ago

Well only the contents of the passkey json property is passed to the library, so that is fine.

For the rest I think I'll have to raise a bug report to https://github.com/MasterKale/SimpleWebAuthn - the JS library I use & possibly with Enpass too (since other password managers work). So looks like one of them have a bug.

Thanks anyways @svgta1

svgta1 commented 3 days ago

I use SimpleWebAuthn too. I've never had problem with it. But, I don't know Enpass. It's seems that they add the feature this year only. The publicKeyAlgorithm and the publicKey have to been given from Enpass. If you don't have them in the json, it's not normal.

Which pubKeyCredParams are you sending when you create the params to send to endpass ? Maybe some alg are not compatible with Enpass and then the datas are note given (but it's should send an error). For my personnal use, I use theses, in order of my preference :

You can find the list of alg compatible with the library there : https://svgtas-organization.gitbook.io/php-webauthn/registration/create-params#pubkeycredparams

svgta1 commented 3 days ago

I reopen the issue. I'm interested to know if you find a solution with the pubKeyCredParams.

mathewparet commented 3 days ago

I use the below algorithms.

  1. PS256
  2. PS384
  3. PS512
  4. RS256
  5. RS384
  6. RS512
  7. ES256
  8. ES384
  9. ES512
  10. Ed256
  11. Ed512
  12. ES256K
mathewparet commented 3 days ago

Also I checked the response generated by Enpass on other websites - like GitHub. Interestingly it has the public key and algorithm in the response. But when used on my website it doesn't seem to do that.

This is how the registration options are generated

public function generateOptions(?PasskeyUser $passkeyUser = null)
    {
        $this->setUser($passkeyUser); // PasskeyUser is just an interface attached to User. So technically its same as the user. 

        $this->webauthn->userVerification->preferred();
        $this->webauthn->residentKey->preferred();
        $this->webauthn->authenticatorAttachment->all();
        $this->webauthn->attestation->none();

        $this->setSupportedPublicKeyParameters();

        return json_decode($this->webauthn->register()->toJson(), true);
    }

public function setUser(?PasskeyUser $passkeyUser = null)
    {
        if($passkeyUser)
        {
            $this->webauthn->user->set(
                name: $passkeyUser->getDisplayName(),
                id: $passkeyUser->getUserId(),
                icon: $passkeyUser->getUserIcon()
            );

            $passkeyUser->passkeys->each(fn($passkey) => $this->webauthn->excludeCredentials->add($passkey->credential_id));
        }

        return $this;
    }

And this is how I process it in JS using SimpleWebAuthn:

const register = () => {
        registrationInProgress.value = true
        form.post(route('passkeys.registration-options'), { // fetch the registration options from server
            preserveScroll: true,
            preserveState: true,
            onSuccess: () => {
                console.log('from server', usePage().props.jetstream.flash.options); // the response is received in `usePage().props.jetstream.flash.options`
                startRegistration(JSON.parse(JSON.stringify(usePage().props.jetstream.flash.options))) // because of all the inertiajs proxying, I have to do JSON.parse and JSON.stringify to remove the proxies and get the actual values.
                    .then((res) =>{
                        form.passkey = res;
                        console.log(res);  // res is the response sent by Enpass and that's what I send to the server to store. This has missing public key and the algorithm.
                        form.post(route('passkeys.store'), {
                            preserveScroll: true
                        })
                    })
                    .catch((err) => console.log(err))
                    .finally(() => closeModal());
            }
        })
    }

Sample output generated by server for registration options, i.e. contents of usePage().props.jetstream.flash:

"options": {
                    "rp": {
                        "name": "Tesla Stats",
                        "icon": "data:image\/svg+xml;utf8,<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"100\" height=\"100\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"#4F46E5\"\/><rect x=\"8\" y=\"6\" width=\"8\" height=\"4\" fill=\"#FFFFFF\"\/><rect x=\"9\" y=\"10\" width=\"6\" height=\"5\" fill=\"#FFFFFF\"\/><path d=\"M8 15h8v2H8z\" fill=\"#FFFFFF\"\/><\/svg>",
                        "id": "tesla-stats.pioneerdynamics.com.au"
                    },
                    "user": {
                        "name": "mathewparet@gmail.com",
                        "id": "MQ",
                        "displayName": "Matt"
                    },
                    "challenge": "9NEj_B9IVtTc2Jw_C24s3ojQRcqhouKyhZg5JMZ4YUYSkECpSmCRW7l0eR-h_HA20-65YsQGk5OMzhohBgJw2Q",
                    "pubKeyCredParams": [
                        {
                            "type": "public-key",
                            "alg": -37
                        },
                        {
                            "type": "public-key",
                            "alg": -38
                        },
                        {
                            "type": "public-key",
                            "alg": -39
                        },
                        {
                            "type": "public-key",
                            "alg": -257
                        },
                        {
                            "type": "public-key",
                            "alg": -258
                        },
                        {
                            "type": "public-key",
                            "alg": -259
                        },
                        {
                            "type": "public-key",
                            "alg": -7
                        },
                        {
                            "type": "public-key",
                            "alg": -35
                        },
                        {
                            "type": "public-key",
                            "alg": -36
                        },
                        {
                            "type": "public-key",
                            "alg": -260
                        },
                        {
                            "type": "public-key",
                            "alg": -261
                        },
                        {
                            "type": "public-key",
                            "alg": -46
                        }
                    ],
                    "timeout": 600000,
                    "excludeCredentials": [
                        {
                            "type": "public-key",
                            "id": "vzYkTJ_bt1UOcqXB9LFBDSgEc3p3C6Qk_x0mQDTfPxY"
                        },
                        {
                            "type": "public-key",
                            "id": "JmTmsvJBBGymvt-ahkmTRw"
                        },
                        {
                            "type": "public-key",
                            "id": "IdxtPi2HcfiPHIDuGu3dI2C1sWQ_bjeNRzRJkUz7Kmc"
                        }
                    ],
                    "authenticatorSelection": {
                        "requireResidentKey": false,
                        "userVerification": "preferred",
                        "residentKey": "preferred"
                    },
                    "attestation": "none"
                }
svgta1 commented 2 days ago

Strange... Normally, webauthn RP must choose the first alg it's can handle with. But, maybe Enpass doesn't do that. Try to change the order of the Alg. Put -257 (RS256) in first and -7 (ES256) in second. If it doesn't work, try -7 in first and -257 in second.

mathewparet commented 1 day ago

Tried the below orders:

-257 -7

and:

-7 -257

The issue persists

svgta1 commented 1 day ago

Strange, strange, strange... And if you try only one alg each time ? Then test every alg, one by one.