Im­pe­ri­alVi­o­let

Test­ing Se­cu­rity Keys (08 Oct 2017)

Last time I re­viewed var­i­ous se­cu­rity keys at a fairly su­per­fi­cial level: basic func­tion, phys­i­cal char­ac­ter­is­tics etc. This post con­sid­ers lower-level be­hav­iour.

Se­cu­rity Keys im­ple­ment the FIDO U2F spec, which bor­rows a lot from ISO 7816-4. Each pos­si­ble trans­port (i.e. USB, NFC, or Blue­tooth) has its own spec for how to en­cap­su­late the U2F mes­sages over that trans­port (e.g. here's the USB one). FIDO is work­ing on much more com­plex (and more ca­pa­ble) sec­ond ver­sions of these specs, but cur­rently all se­cu­rity keys im­ple­ment the basic ones.

In essence, the U2F spec only con­tains three func­tions: Reg­is­ter, Au­then­ti­cate, and Check. Reg­is­ter cre­ates a new key-pair. Au­then­ti­cate signs with an ex­ist­ing key-pair, after the user con­firms phys­i­cal pres­ence, and Check con­firms whether or not a key-pair is known to a se­cu­rity key.

In more de­tail, Reg­is­ter takes a 32-byte chal­lenge and a 32-byte appID. These are in­tended to be SHA-256 hashes, but are opaque and can be any­thing. The chal­lenge acts as a nonce, while the appID is bound to the re­sult­ing key and rep­re­sents the con­text of the key. For web browsers, the appID is the hash of a URL in the ori­gin of the login page.

Reg­is­ter re­turns a P-256 pub­lic key, an opaque key han­dle, the se­cu­rity key's batch cer­tifi­cate, and a sig­na­ture, by the pub­lic key in the cer­tifi­cate, of the chal­lenge, appID, key han­dle, and pub­lic key. Since the se­cu­rity keys are small de­vices with lim­ited stor­age, it's uni­ver­sally the case in the ones that I've looked at that the key han­dle is ac­tu­ally an en­crypted pri­vate key, i.e. the token of­floads stor­age of pri­vate keys. How­ever, in the­ory, the key han­dle could just be an in­te­ger that in­dexes stor­age within the token.

Au­then­ti­cate takes a chal­lenge, an appID, and a key han­dle, ver­i­fies that the appID matches the value given to Reg­is­ter, and re­turns a sig­na­ture, from the pub­lic key as­so­ci­ated with that key han­dle, over the chal­lenge and appID.

Check takes a key han­dle and an appID and re­turns a pos­i­tive re­sult if the key han­dle came from this se­cu­rity key and the appID matches.

Given that, there are a num­ber of prop­er­ties that should hold. Some of the most crit­i­cal:

  • The key han­dle should be en­crypted; i.e. we shouldn't be able to find an ECDSA pri­vate key in there.
  • A key han­dle from one se­cu­rity key should not work with an­other, even of the same type.
  • If a se­cu­rity key is asked to gen­er­ate hun­dreds of key-pairs, they should all be dis­tinct.
  • All the sig­na­tures should have unique nonces, oth­er­wise you have the PlaySta­tion bug and can ex­tract pri­vate keys.
  • The appID should ac­tu­ally be checked.

But there are a num­ber of other things that would be nice to test:

  • Does the key han­dle have pat­terns? Ide­ally it would be in­dis­tin­guish­able from ran­dom to out­side ob­servers.
  • Is the key han­dle mu­ta­ble? Re­ally the whole thing should be au­then­ti­cated.
  • Are the sig­na­tures cor­rectly en­coded? It's a very sim­ple struc­ture, but it's ASN.1 so there are cor­ner-cases.
  • The USB pro­to­col trans­mits 64-byte pack­ets, the padding bytes at the end should all be zero, not ran­dom bits of mem­ory. Are they?

So given all those de­sir­able prop­er­ties, how do var­i­ous se­cu­rity keys man­age?

Yu­bico

Easy one first: I can find no flaws in Yu­bico's U2F Se­cu­rity Key.

VASCO Se­cureClick

I've ac­quired one of these since the round-up of se­cu­rity keys that I did last time so I'll give a full in­tro­duc­tion here. (See also Brad's re­view.)

This is a Blue­tooth Low-En­ergy (BLE) token, which means that it works with both An­droid and iOS de­vices. For non-mo­bile de­vices, it in­cludes a USB-A BLE don­gle. The Se­cureClick uses a Chrome ex­ten­sion for con­fig­ur­ing and pair­ing the don­gle, which works across plat­forms. The don­gle ap­pears as a nor­mal USB de­vice until it sees a BLE sig­nal from the token, at which point it “dis­con­nects” and re­con­nects as a dif­fer­ent de­vice for ac­tu­ally doing the U2F op­er­a­tion. Once an op­er­a­tion that re­quires user-pres­ence (i.e. a Reg­is­ter or Au­then­ti­cate) has com­pleted, the token pow­ers down and the don­gle dis­con­nects and re­con­nects as the orig­i­nal USB de­vice again.

If you're using Linux and you con­fig­ure udev to grant ac­cess to the ven­dor ID & prod­uct ID of the token as it ap­pears nor­mally, noth­ing will work be­cause the ven­dor ID and prod­uct ID are dif­fer­ent when it's ac­tive. The Chrome ex­ten­sion will get very con­fused about this.

How­ever, once I'd fig­ured that out, every­thing else worked well. The prob­lem, as is in­her­ent with BLE de­vices, is that the token needs a bat­tery that will run out even­tu­ally. (It takes a CR2012 and can be re­placed.) VASCO claims that it can be used 10 times a day for two years, which seems plau­si­ble. I did run the bat­tery out dur­ing test­ing, but test­ing in­volves a lot of op­er­a­tions. Like the Yu­bico, I did not find any prob­lems with this token.

I did have it work­ing with iOS, but it didn't work when I tried to check the bat­tery level just now, and I'm not sure what changed. (Per­haps iOS 11?)

Feit­ian ePass

ASN.1 DER is de­signed to be a “dis­tin­guished” en­cod­ing, i.e. there should be a unique se­ri­al­i­sa­tion for a given value and all other rep­re­sen­ta­tions are in­valid. As such, num­bers are sup­posed to be en­coded min­i­mally, with no lead­ing zeros (un­less nec­es­sary to make a num­ber pos­i­tive). Feit­ian doesn't get that right with this se­cu­rity key: num­bers that start with 9 lead­ing zero bits have an in­valid zero byte at the be­gin­ning. Pre­sum­ably, num­bers start­ing with 17 zero bits have two in­valid zero bytes at the be­gin­ning and so on, but I wasn't able to press the but­ton enough times to get such an ex­am­ple. Thus some­thing like one in 256 sig­na­tures pro­duced by this se­cu­rity key are in­valid.

Also, the final eight bytes of the key han­dle seem to be su­per­flu­ous: you can change them to what­ever value you like and the se­cu­rity key doesn't care. That is not im­me­di­ately a prob­lem, but it does beg the ques­tion: if they're not being used, what are they?

Lastly, the padding data in USB pack­ets isn't ze­roed. How­ever, it's ob­vi­ously just the pre­vi­ous con­tents of the trans­mit buffer, so there's noth­ing sen­si­tive get­ting leaked.

Thetis

With this de­vice, I can't test things like key han­dle mu­ta­bil­ity and whether the appID is being checked be­cause of some odd be­hav­iour. The re­sponse to the first Check is in­valid, ac­cord­ing to the spec: it re­turns sta­tus 0x9000 (“NO_ER­ROR”), when it should be 0x6985 or 0x6a80. After that, it starts re­ject­ing all key han­dles (even valid ones) with 0x6a80 until it's un­plugged and rein­serted.

This de­vice has the same non-min­i­mal sig­na­ture en­cod­ing issue as the Feit­ian ePass. Also, if you click too fast, this se­cu­rity key gets upset and re­jects a few re­quests with sta­tus 0x6ffe.

USB padding bytes aren't ze­roed, but ap­pear to be parts of the re­quest mes­sage and thus noth­ing in­ter­est­ing.

U2F Zero

A 1KiB ping mes­sage crashes this de­vice (i.e. it stops re­spond­ing to USB mes­sages and needs to be un­plugged and rein­serted). Test­ing a cor­rupted key han­dle also crashes it and thus I wasn't able to run many tests.

KEY-ID / Hy­per­FIDO

The Key-ID (and Hy­per­FIDO de­vices, which have the same firmware, I think) have the same non-min­i­mal en­cod­ing issue as the Feit­ian ePass, but also have a sec­ond ASN.1 flaw. In ASN.1 DER, if the most-sig­nif­i­cant bit of a num­ber is set, that num­ber is neg­a­tive. If it's not sup­posed to be neg­a­tive, then a zero pad byte is needed. I think what hap­pened here is that, when test­ing the most-sig­nif­i­cant bit, the se­cu­rity key checks whether the first byte is > 0x80, but it should be check­ing whether it's >= 0x80. The up­shot is the some­times it pro­duces sig­na­tures that con­tain neg­a­tive num­bers and are thus in­valid.

USB padding bytes aren't ze­roed, and in­clude data that was not part of the re­quest or re­sponse. It's un­likely to be ma­te­r­ial, but it does beg the ques­tion of where it comes from.

The wrapped keys also have some un­for­tu­nate prop­er­ties. Firstly, bytes 16 through 31 are a func­tion of the de­vice and the appID, thus a given site can pas­sively iden­tify the same token when used by dif­fer­ent ac­counts. Bytes 48 through 79 are unau­then­ti­cated and, when al­tered, every­thing still works ex­cept the sig­na­tures are wrong. That sug­gests that these bytes are the en­crypted pri­vate key (or the en­crypted seed to gen­er­ate it). It's not ob­vi­ous that there's any vul­ner­a­bil­ity from being able to tweak the pri­vate key like this, but all bytes of the key han­dle should be au­then­ti­cated as a mat­ter of best prac­tice. Lastly, bytes 32 through 47 can't be ar­bi­trar­ily ma­nip­u­lated, but can be sub­sti­tuted with the same bytes from a dif­fer­ent key han­dle, which causes the sig­na­tures to be in­cor­rect. I don't know what's going on there.

Over­all, the key han­dle struc­ture is suf­fi­ciently far from the ob­vi­ous con­struc­tion to cause worry, but not an ob­vi­ous vul­ner­a­bil­ity.