Skip to content
This repository has been archived by the owner on Feb 14, 2024. It is now read-only.

Minimal FIDO2 Support (to support iOS) #12

Open
darconeous opened this issue Mar 11, 2020 · 28 comments
Open

Minimal FIDO2 Support (to support iOS) #12

darconeous opened this issue Mar 11, 2020 · 28 comments

Comments

@darconeous
Copy link

darconeous commented Mar 11, 2020

iOS now supports NFC FIDO2 CTAP2 authenticators. Unfortunately, Apple doesn't support the use of NFC U2F/CTAP1 authenticators, with or without the appid extension. Thus, this applet is currently not compatible with iOS devices when used with NFC.

(It is unclear if iOS supports CTAP1 via USB, but Safari on macOS does support CTAP1 for USB devices)

I'm proposing writing a small FIDO2 "wrapper" that will enable this applet to be compatible with iOS devices. Such a wrapper would effectively make the applet a FIDO2 compliant token that doesn't support resident keys or pin codes, but it would work with iOS. It wouldn't work with all services (like those that require pin codes), but it would work with any service that previously worked with a U2F token.

I mention this because it is likely the path of least resistance to implementing something that works with iOS: Just take the existing U2F/CTAP1 authenticator and implement a little CBOR parsing to extract the fields, do the U2F/CTAP1 processing, and then wrap that in more CBOR to return a FIDO2 result. Easy-ish.

The alternative is to effectively start an entirely new FIDO2 authenticator project, which is a lot of work.

@darconeous
Copy link
Author

Argh, parsing CBOR in Java Card is a total pain. I did find a CBOR encoder/decoder in this project, which is also Apache v2 licensed. It's bare bones though, and kind of a bear to use... But that's what I'd expect for doing something like this in Java Card.

@martinpaljak
Copy link
Contributor

Pardon my ignorance as I have never fully followed the spec: what is the evolution of u2f to fido2 and can you point me to the card api specs and what is missing?

@darconeous
Copy link
Author

darconeous commented Mar 16, 2020

It's a little complicated and subtle. I had to ask StackOverflow about some details myself before I fully understood it. But I'll explain it here for posterity:

U2F and CTAP1

First there was U2F. U2F had two operations: Register and Authenticate. Both took a field called the Application Parameter, which in U2F is defined to be the SHA256 hash of the "application identifier" (app id), which is a URL like "http://example.com". U2F defines its protocol in terms of APDUs.

FIDO2 was designed to be usable with existing U2F security keys. However, FIDO2 has no concept of an application parameter/identifier. Instead, it has a concept of a "relying party identifier", which is a string that is a domain name. So FIDO2 simply takes the existing U2F protocol, redefines how you calculate the "Application Parameter" (now calculated from the SHA256 of the "relying party identifier"), and calls that resulting protocol CTAP1.

So, to be clear:

  • U2F uses an "authentication parameter" that is calculated from a URL.
  • CTAP1 is the use of the U2F protocol in a way that is compatible with FIDO2 but incompatible with U2F services—because the "application parameter" being calculated from the FIDO2 "relying party identifier".
  • U2F and CTAP1 differ only in how that single parameter is calculated.

While the authenticators are the same, old U2F registrations are incompatible with FIDO2 registrations. To work around this, there is the "appid" extension in Webauthn that provides the "legacy" U2F URL to the browser so that it can try passing a hash of that instead of the hash of the relying party ID. This way people can still log-in once a site upgrades to Webauthn.

U2F (based on app ids) is effectively deprecated by FIDO2, but CTAP1 (based on relying party identifiers) is not. Since the only difference that matters between the two is at the service/website level (U2F vs Webauthn), all U2F security keys are also CTAP1 security keys. So the security keys themselves aren't deprecated, just the way in which they were originally used is.

CTAP2

Support for CTAP1 is optional on new FIDO2 authenticators (security keys). CTAP2 is where all of the fancy new FIDO2 action happens. CTAP2 is an entirely new non-APDU-based protocol that uses CBOR for encoding structured requests and responses. The protocol handles a lot more cases than CTAP1, such as resident keys, user verification, and even symmetric secret key generation (via the hashmac-secret extension).

CTAP1 and CTAP2

The magic is that if you don't specify any features that couldn't be satisfied by a CTAP1 authenticator, then the resulting parameters are defined to be calculated in the exact same way. In this way, CTAP2 is defined to be a functional superset of CTAP1.

Indeed, the official CTAP documentation outlines how CTAP2 maps onto CTAP1. This is assumed to be built into the platform/web-browser.

If the browser or platform supports CTAP1, then everything just works. However, support for CTAP1 is optional, in both the authenticator and the platform. So far, it seems like everyone who is implementing support for Webauthn is supporting both CTAP1 authenticators and CTAP2 authenticators, with one notable exception: iOS.

So what can we do?

Well, we could just write an entirely new FIDO2 authenticator. But that might not be necessary if we only want to support the U2F use case: If we can perform the CTAP2/CTAP1 translation inside the authenticator, then perhaps that could be something that we can just add to this project that would allow it to be used on iOS.

Doing so has the following challenges:

  • Parsing CBOR in the limited environment of Java Card is a PITA. It's doable, but sheesh, it's likely going to double the code size.
  • CTAP1 only supports testing a single key handle at a time, whereas CTAP2 handles several at once. This makes it not an exact one-to-one mapping, but it's still doable.

Hopefully that clarifies things. I'm slowly trying to write something up myself, but it will likely be another week or two before I can find enough time to actually get something workable put together. I'm aiming to just get the authentication/assertion step working first and then I'll throw it up on GitHub.

But I also don't want to stop anyone from giving it a try themselves. Who knows, I could get distracted and not end up finishing it (I'm not getting paid to do this). If anyone else wants to work on this, please do. I'll be happy to share my non-functional work-in-progress if it would help.

I'll keep this thread updated.

@darconeous
Copy link
Author

Actually... I looks like this might be a bug in the way that iOS implements their NFC authenticator interface. It looks like if I just add support for the CTAP2 GetInfo command and only indicate that it supports U2F_V2... then it seems to use CTAP1!

Registration doesn't seem to work, but then again that has never worked well on iOS.

@darconeous
Copy link
Author

It looks like registration isn't working because there is a bug in the handling of extended APDU registration requests.

This extended registration APDU doesn't work (fails with 6F00):

00010300 00 00400EE918648C1A7BE1353D7A9372A099DB14D9E6B97EBD90B829FEF5C957554DBC9569088F1ECEE3232954035DBD10D7CAE391305A2751B559BB8FD7CBB229BDD40000

But this normal registration APDU does work:

00010300 40 0EE918648C1A7BE1353D7A9372A099DB14D9E6B97EBD90B829FEF5C957554DBC9569088F1ECEE3232954035DBD10D7CAE391305A2751B559BB8FD7CBB229BDD4

I bet this is just a simple oversight somewhere in the code.

@darconeous
Copy link
Author

By the way, the key to making U2F/CTAP1 keys work with NFC on iOS is to handle this APDU:

80100000 00  0001040000

and then respond with this:

00A10181665532465F5632 9000

Which is just an CTAP OK response and {1:["U2F_V2"]} encoded as CBOR.

If the applet can do that then it can respond to authenticate requests properly on iOS. Registration requests still won't work for the reason outlined above.

@martinpaljak
Copy link
Contributor

I hope to find my good card for testing things out. Do you have a branch you work on?

@darconeous
Copy link
Author

I'll push a branch out and let you know once I get things working and cleaned up.

@darconeous
Copy link
Author

darconeous commented Mar 17, 2020

And here it is in two commits:

  • 53f8290 Fix for Extended APDUs
  • 7a72527 Work-around for Native iOS Support

It's available on this branch.

To be honest, this looks like a plain old bug in iOS, so 7a72527 may not ultimately be necessary. But 53f8290 definitely will be, even once the bug is fixed in iOS.

@martinpaljak
Copy link
Contributor

Great, Will have a look at the "Javacard parts" of it. Not a huge user of fido things, where does one test the registration + usage flow these days?

@darconeous
Copy link
Author

https://webauthn.org is what I've been using, but you can also use https://demo.yubico.com/webauthn-technical/registration.

@darconeous
Copy link
Author

Here is a quick installer script using your GlobalPlatformPro tool that you might find helpful: https://gist.github.com/darconeous/adb1b2c4b15d3d8fbc72a5097270cdaf

@martinpaljak
Copy link
Contributor

Gave it a try. Nothing worked with Chrome and Samsung s10e nor iphone xs :) Might be my phone, as iphone does not detect NDEF messages either (used to, but not any more). Or a crappy form-factor for the JavaCard (a ring. will try a few others).

Also looked a bit into the specs and the code in detail. I think it makes sense to write a new authenticator, that would have some integrity built into it as well. Wrote some notes here: https://github.com/martinpaljak/javacard-fido-applet/wiki

Will need to read the specs in full, right now just browsed a bit (coronavirus quarantine with kids, yay!). Setting up a repeatable test environment with repeatable instructions would be a must have.

@darconeous
Copy link
Author

If the ring you are using is the NFC Ring Omni, know that it is a Type B card, and I have yet to find a device that will use a Type B FIDO2 authenticator. They all seem to want Type A.

I made my own Type A ring and it works great on both my iPhone 8 and Nexus 5. Also tested with a NXP P71 card configured for Type A and it also worked great.

@martinpaljak
Copy link
Contributor

Thank you for the tip, it is indeed this ring and indeed TypeB, dang! Had hoped it would work. Will try with a TypeA token instead ....

And with TypeA token registration on the demo app succeeds with iphone and your ios branch, authentication fails ("found no credentials on this device"). Android fails on registration "no supported app for this nfc tag" after touching once the "usb/nfc/ble" selection window appears, might be I don't know how to properly use Android.

I have taken what you have written about iOS, digged through the u2f specs (mostly old v1) and written an applet from scratch, that should have the necessary structure to make it resilient and "extendable" and almost-production-ready-secure as well. But I'll continue with that in the repo linked above.

@darconeous
Copy link
Author

Weird. I bet there are some additional differences between JavaCard runtimes that might be throwing it off. What type of card are you using? I've tested with an NXP J3H145 and an NXP P71 (not sure the JCOP name tag) and it worked flawlessly on both.

I did make a few changes to the branch though after testing, maybe those threw things off. Will check.

@darconeous
Copy link
Author

Ah, I totally messed it up in my post-testing changes. Another reminder to me to always test! Argh!

@darconeous
Copy link
Author

Ok, I just added a4a83aa to the iOS-support branch. iOS should work fine now. Not sure what's up with your android phone.

@darconeous
Copy link
Author

darconeous commented Mar 18, 2020

By the way, from your wiki notes:

  • attestation key + certificate
    • to know which device it is

Except under specific circumstances (security keys issued by a company strictly for use by their employees to access company resources, for example) the attestation key and certificate are used to know what kind of device it is, not which device it is. The same certificate and key are intended to be used across all devices in a lot, should should be large enough to include thousands of physical keys. This was done for privacy reasons.

AES-256 for wrapping on-device generated per-site keys, to be stored in the authenticator?

For the U2F use case, the site keys are typically encrypted and stored in the key handle. That way the number of keys is not limited by the storage space of the authenticator.

FIDO2 supports a concept called "resident keys", which works a bit differently than the U2F flow. Those keys are guaranteed to be stored on the authenticator. Unfortunately there is no standardized way to manage such registrations so your only option if you run out of space is to reset the whole security key—which is lousy. This is one of the reasons I'm not a fan of resident keys.

You might find this article interesting about key wrapping, which describes a method that is different than what this project is doing. That method seems to be what the CCU2F project is using.

@martinpaljak
Copy link
Contributor

All noted and read.
Question: as extended APDU support seems to be optional (?) will client implementations fall back to it?

@darconeous
Copy link
Author

darconeous commented Mar 18, 2020

Extended APDU support (as well as standard APDU support) is a "MUST" for NFC FIDO U2F authenticators, according to that spec. Same thing for FIDO2:

Authenticators MUST support short and extended length encoding for this APDU.

On the other hand, platforms need only support one or the other. There seems to be no guidance as to which is preferred.

@darconeous
Copy link
Author

FYI: After some more hacking, my personal fork has diverged a bit. I've made bunch of additional changes (multiple counters, support for dont-enforce-user-presence-and-sign, etc.). I've put them on the master branch.

@ckahlo
Copy link

ckahlo commented Mar 21, 2020

Hi Robert, Hi Martin,

BTW: have you seen VK-U2F with CTAP2 support?
You both (Robert & Martin) seem not be on the FIDO-DEV mailing list. Some questions should be addressed there instead of Ledger GitHub Repo. At least you'll get attention from more devs familiar with CTAP1/CTAP2.

We've also done another fork for tests from Tobias CCU2F implementation w/o custom NXP APIs, extended length support for small APDU buffers, CTAP2 minimal "support" (version & invalid command), reworked counter designs, etc. Below is my increment counter function. The counters
array is 2-dimensional and built similar to FIDELIO eService_V1_1 chapter 6.2. I'm explaining key-derivation in chapter 5 as well. Tobias implemented a little bit different approach to ensure (certifiable) sufficient randomness on JavaCard.

Best regards,
Christian

private void increment_ctr(final byte[] counter) {

        // Increase the counter
        JCSystem.beginTransaction();
		boolean carry = ++counter[counter.length - 1] == 0;
		for (byte i = 1; carry && i < counter.length; i++) {
			carry = ++counter[(short) (counter.length - 1 - i)] == 0;
		}
        JCSystem.commitTransaction();
        
        if (carry) { // Game over
            counterOverflowed = true;
            ISOException.throwIt(ISO7816.SW_FILE_FULL);
        }		
}

@ckahlo
Copy link

ckahlo commented Mar 21, 2020

BTW: there is another good reason to split up the counters for tokens that may be used very often and for a long period of time (rings, implants, keyfobs ...): EEPROM write endurance.

@darconeous
Copy link
Author

darconeous commented Mar 23, 2020

BTW: have you seen VK-U2F with CTAP2 support?

Yes. I reached out to Riley about it (and my own commits) a few days ago. However, if you take a closer look at the diff you will notice that the CTAP2 support is just a boilerplate.

I actually got fairly far in my own CTAP2 implementation (full parsing and authentication, no registration though) before I realized that I could get iOS compatibility by just returning a CTAP2 error.

You both (Robert & Martin) seem not be on the FIDO-DEV mailing list. Some questions should be addressed there instead of Ledger GitHub Repo. At least you'll get attention from more devs familiar with CTAP1/CTAP2.

I belong to way too many mailing lists already. I prefer web forums these days in order to avoid information overload.

We've also done another fork for tests from Tobias CCU2F implementation w/o custom NXP APIs, extended length support for small APDU buffers, CTAP2 minimal "support" (version & invalid command), reworked counter designs, etc.

Nice, is the fork public? I'd love to have a look.

Below is my increment counter function. The counters
array is 2-dimensional and built similar to FIDELIO eService_V1_1 chapter 6.2. I'm explaining key-derivation in chapter 5 as well.

there is another good reason to split up the counters for tokens that may be used very often and for a long period of time (rings, implants, keyfobs ...): EEPROM write endurance.

Mine is a bit more heavy-handed in some ways, but less heavy-handed in others. For example, I have a Counter class which implements all of the wear-leveling. One nice feature is that my counter is atomic without relying on explicit transactions. To implement multiple counters, I have an array of counters which I assign round-robin-style as key handles are generated.

Tobias implemented a little bit different approach to ensure (certifiable) sufficient randomness on JavaCard.

I really like that approach (which I assume is similar to the Yubico method), and I'd use it if I had more cards that had support for JC 3.0.5. Since this is a hobby project I refuse to sign any NDAs, so I'm somewhat limited in what APIs I can use.

@epicfacethe3rd
Copy link

is fido2 support still planned?

@darconeous
Copy link
Author

Not on my fork, no. It works good enough for me.

@emvuys
Copy link

emvuys commented Aug 4, 2020

FIDO2 will be shared on github?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants