[DISCLAIMER: I’m not a java programmer, I mostly write code using C as a good macro assembler.]
Quick link to the repository with all the relevant patches
Android’s a fairly bad platform from a security point of view, with malware-serving advert libraries and all sorts of misery. I miss the halcyon days of being able to use a phone to log into my computers without having to carry around a small book of one-time passwords.
So whilst I was generally upgrading security everywhere else this week (switching to smartcard HSMs for login etc.) I discovered there are smartcards that do RSA over NFC. My phone has NFC – wouldn’t it be nice if I could just use the smartcard with ssh on my phone to access my computers. How hard can that be? Moderately miserable it turns out.
First there’s the hardware: there are number of different protocols in ISO 7816 for speaking to cards. RSA signatures are typically larger than the 256 bytes of a regular APDU, and so most cards need to use extended APDUs. There are remarkably few NFC card readers that support extended APDUs, and even fewer that correctly advertise the fact in their CCID descriptors. For example the Broadcom chips in most Dell laptops claim they can’t but do actually work, but the ubiquitous ACR122U can’t, nor claims to.
The smartcards I was initially using suffered from this problem and failed to work with almost all the NFC readers I have, including the ones in my cellphones. Fortunately there are some cards which manage to split large APDUs into smaller 256 byte chunks using ’61xx` statuses. The YubiKey NEO is an example of one such card.
From the android side of things it’s worse: there’s no native pkcs11 type interface, nor any software to talk to a typical ISO 7816 HSM, and almost no phones support extended APDUs. At first I compiled up pcsc-lite to run natively on andorid, and used the smartcard-reader app to talk to the pcsc-lite ifdvpcd plugin via TCP to localhost. That’s horrible, flaky and has terrible UX. The software that does exist, is expecting to talk to a CCID reader over USB.
The YubiKey however also supports the OpenPGP card standard for which there exists an OSS android application called OpenKeychain. Even better, OpenKeychain, has an API which allows other applications to request signatures &c.
The desktop versions of the gnupg tools even ship with a daemon which is an ssh agent and will serve signatures from an attached openpgp card. Better still in the typical configuration the cards even have a subkey reserved purely for authentication.
OpenKeychain’s API however, is very much structured around PGP signatures which, as well as hashing the user supplied data, also hash a series of sub-packets containing information the the time and the keys used. That means that those signatures can’t be used for SSH, so I extended OpenKeychain‘s openpgp-api with one more method which can be used for SSH authentication.
OpenKeychain uses the Bouncy Castle pgp library, and there’s no easy way, without making a very large number of changes to the code, to get it to make plain signatures. However, it is possible to cheat (read use an ugly hack): when an NFC card is in use, OpenKeychain swaps Bouncy Castle‘s
NfcSyncPGPContentSignerBuilder which throws an exception containing the final hash that the PGP stack would like the NFC card to sign. This is caught in the function which called into the PGP stack and is then passed to the NFC stack. It’s trivial at this point to swap out the hash with one that’s been made just over the data, and not the whole pgp structure. The complete patch is here and also references the update to the openpgp-api submodule above. The only subtleties are that the openpgp card doesn’t use the MSE (MODIFY SECURITY ENVIRONMENT) command to select the key used for signing, instead you use the INTERNAL AUTHENICATE command (0x88) to implicitly select the authentication key, and log in using PW2 instead of PW1.
Next up is making an android ssh client work with all of this. ConnectBot proudly claims to be “the first SSH client for Android” and seems to be under active development again. It’s OSS so it’s a perfect starting point.
ConnectBot uses a fork of trilead’s java ssh2 library. This SSH library is pure java code and not android code. Unfortunately, as dealing with with NFC cards is going to require some sort of user interaction, it needs to know a bit about android. I added a new type of private key class to represent an RSA key on the token, which just contains the 64bit pgp key fingerprint of the master private key we’d like to use, and a fairly budget class to do the actual signatures. As the guts of ConnectBot run in a service, it’s not able to make UI requests except via notifications. To get round this I have the
TerminalBridge class inform the
TokenRSASHA1Verify class whenever ConnectBot has an running Activity so it can make use of it to fire requests at OpenKeychain. If there’s no activity, the authentication silently fails. We catch the replies from OpenKeychain‘s pending intends in
ConsoleActivity and ship them back to
TokenRSASHA1Verify to complete the authentication. It’s fairly ugly and we leave a thread stuck sleeping whilst it all goes down. However it does work.
Of course you need to get the key set up, and the public key loaded into ConnectBot. In an ideal world I’d have written some UI to do this, but for the moment you need to do it by hand.
First, if you haven’t already, fetch the pgp keys from the yukikey NEO into to your local pgp keyring.
[hsm@lamia ~]$ gpg --card-edit --keyid-format LONG gpg: detected reader `ACS ACR122U PICC Interface 00 00' Application ID ...: D2760001240102000006045475740000 [...] Signature counter : 1248 Signature key ....: A2F6 5929 B9AB 0BC8 E414 9567 B777 CD49 EED7 5980 created ....: 2016-05-25 13:27:08 Encryption key....: 2CFF 1819 70B9 6F9E 41BC 0581 DDAF 1D10 D0F1 9E72 created ....: 2016-05-25 13:27:08 Authentication key: 23C3 E170 3A4D CC52 F4A5 B6C7 6FA2 B3CA 28B6 6590 created ....: 2016-05-25 13:27:08 General key info..: [none] gpg/card> fetch gpg: requesting key E41441BC from hkp server keys.gnupg.net gpg: key E41441BC: public key "Cardy McCardface <firstname.lastname@example.org>" imported gpg: Total number processed: 1 gpg: imported: 1 (RSA: 1) gpg/card> quit [hsm@lamia ~]$ gpg --keyid-format LONG --list-keys /home/hsm/.gnupg/pubring.gpg ---------------------------- pub 2048R/3770FE13E9B15794 2016-05-25 uid Cardy McCardface <email@example.com> sub 2048R/BD13A2C2152CCA57 2016-05-25 sub 2048R/08DFE9056F4DE18F 2016-05-25 [hsm@lamia ~]$ gpg --keyid-format LONG --edit-key 3770FE13E9B15794 [...] pub 2048R/3770FE13E9B15794 created: 2016-05-25 expires: never usage: SC trust: unknown validity: unknown sub 2048R/BD13A2C2152CCA57 created: 2016-05-25 expires: never usage: A sub 2048R/08DFE9056F4DE18F created: 2016-05-25 expires: never usage: E [ unknown] (1). Cardy McCardface <firstname.lastname@example.org> gpg> quit
The Key for authentication is marked with the “A”. In the list above: it’s BD13A2C2152CCA57. Now that you know the 64 bit key fingerprint for the authentication key, extract it as an openssh compatible public key.
[hsm@lamia ~]$ gpgkey2ssh BD13A2C2152CCA57 > id_rsa.pub [hsm@lamia ~]$ cat id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwUtarzIo6vyrsS18pXk3vKuPXYXpWFpxijsRbM65ucOVSXIE60MEiw4gvzyvTI9+UqK+7MZxCcIxSZABEDGGoimW5wrQM+Anetgn68JiWWZnjlPTeO5i56LwjhsfK8O7IEwkYQhzCVX1voy8n+x27MFG/fdbNnLWAJc4OhU7iC5+9dfzukkIi/fDA8xK0ctqoytS2LMKY5R8rsYvzRV246+t1NKy5CfhrTjiylDAnwj8Hmh/Yq0DhyoUclDidc8gqIMkvSxYkKpYx0go1BGva6i5Oke3VDopT2PEpe5bzNcyvbmKzP61QBJXijA1zjG3MGeSa0VqkLTqwavdaS4eP email@example.com [hsm@lamia ~]$
Lastly make the file ConnectBot needs to import the key. (Here 3770FE13E9B15794 is the fingerprint of the master key above, which is what open-keychain needs to identify the key.)
[hsm@lamia ~]$ ssh-keygen -f fish.pub -e -m pem | sed '1 a Private-Key-ID: 3770FE13E9B15794' > connect_bot_key.pem [hsm@lamia ~]$ cat connect_bot_key.pem -----BEGIN RSA PUBLIC KEY----- Private-Key-ID: 3770FE13E9B15794 MIIBCgKCAQEAsFLWq8yKOr8q7EtfKV5N7yrj12F6VhacYo7EWzOubnDlUlyBOtDB IsOIL88r0yPflKivuzGcQnCMUmQARAxhqIplucK0DPgJ3rYJ+vCYllmZ45T03juY uei8I4bHyvDuyBMJGEIcwlV9b6MvJ/sduzBRv33WzZy1gCXODoVO4gufvXX87pJC Iv3wwPMStHLaqMrUtizCmOUfK7GL80VduOvrdTSsuQn4a044spQwJ8I/B5of2KtA 4cqFHJQ4nXPIKiDJL0sWJCqWMdIKNQRr2uouTpHt1Q6KU9jxKXuW8zXMr25isz+t UASV4owNc4xtzBnkmtFapC06sGr3WkuHjwIDAQAB -----END RSA PUBLIC KEY----- [hsm@lamia ~]$
Copy the keyfile onto your device and import it into ConnectBot using the “Manage Pubkeys” menu item, then click on the icon of a folder in the top right. When you first import they key, it’ll be locked and you’ll need to tap it to unlock it (the padlock icon unlocks). Once you’ve done that it should remain unlocked forever (the concept of locking doesn’t make much sense when the private key isn’t actually on the device).
Make sure OpenKeychain is configured and has imported and bound they keys [The banner at the top of the screen when you present the token after starting the app should be green]
The first time you try to authenticate, OpenKeychain will ask you if you want to let ConnectBot use it, and the authentication will likely fail. Say yes, and then disconnect the session and try again.
When everything works you should get a prompt like this to use the token.
And you should be able to log in. The next obvious thing to do would be to implement ssh-agent forwarding so that you could use the YubiKey to ssh from one host to another from a session started on the phone.