| U2F is a two step process - you need to register the device first (through challenge-response process called ENROLLMENT), then you can use it by issuing authentication challenge (U2F authentication is NOT the same as user authentication, they labeled the process as ENROLLMENT - AUTH process). During registration you receive several pieces of info, such as keyHandle, public key you use to verify future device responses and an attestation certificate so you can verify the device vendor. The interesting part is this: when you challenge the u2f device to sign AUTHENTICATION request, what it does is keep a counter tied to your appId (the domain where the .js that deals with this is executed) and it produces encoded json which is signed by the device, using an EC private key. The counter increases every time the device is challenged by that particular appId. The response is signed using the counter and a private key that's on the device (which you can't tamper with). So, the phishing / mitm should have the value of private key and the moving part (counter) that's tied to a specific appId. That's difficult since it SHOULD do this during enrollment process and every subsequent authentication request. What's important that not only does it protect against phishing but from replays too. Naturally, the u2f device isn't standalone responsible for this, the verifying server implementation is a crucial part of the process. Disclaimer: I'm not affiliated by Yubico, but have implemented U2F (the dreaded js part and backend part) in 2015, several months after Chrome 38 has been released, the first version supporting u2f protocol. |
(If you use a Ledger device for U2F and subsequently restore a new (or a reset) device from your private seed the counter will be reset. Trezor has the same issue but allows you to manually set the counter to work around it.)