ssh with yubikey neo on android

[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.

ssh-code-book

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.yubi-key-neo

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 JcaPGPContentSignerBuilder for 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 <spammesenseless@sittingduck.net>" 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 <spammesenseless@sittingduck.net>
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 <spammesenseless@sittingduck.net>

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 hsm@lamia.local
[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).

connectbot-locked-key

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.

connectbot-allow-openkeychain

When everything works you should get a prompt like this to use the token.

connectbot-token-promptAnd 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.

8 thoughts on “ssh with yubikey neo on android”

  1. Hello,

    I’m blocking at the step of copying the publickey to ConnectBot. When importing, ConnectBot tells me that it isn’t a valid private key. How did you manage to import it?

    Thanks for that tutorial anyway!

  2. Hi James. Thanks very much for your work!!! I’ve been looking for this for ages. I’ve even tried to make this work myself a few years ago (back when Android studio did not exist yet) but I couldn’t work it out with that issue with interacting from the service. I simply have no experience in Android development.

    I’ve managed to build the apps as you have them in your repo. It was a bit difficult as I’ve never used Android Studio before 🙂 Didn’t know how to include a lib. But I think I managed (I’ve only done a small bit of iOS development before).

    But when I create my PEM file exactly as you mention, I get an error from ConnectBot that it can’t parse the PEM file. Would you have any idea? I see more detailed error codes are raised by the PEMDecoder class but I don’t think that detail makes it through to the user interface.

    Of course I checked if the right version of SSHLib is included – I see the text “Private-Key-ID” in the final compiled class in the connectbot project. I think that was added by you, right? So it should be OK I think.

    I just can’t figure this part out strange enough.. Just wondering if you had similar issues.

    1. By the way, I know this is not the recommended way of doing this. But I’ve been following the tickets on github for ConnectBot and OpenKeyChain to merge this, and while it looks like they decided how it should be be done, I see they don’t have any dev time to spend on it so this could take forever 🙂 That’s why I thought I’d try to build your sources.

  3. PS (sorry for all the comments here), I also tried your example key above. It also doesn’t work. I doublechecked that your changes have made it to my compiled version though. I checked the hex dump of the ‘classes.dex’ file and I see stuff from the code, such as the error messages about Private-Key-ID and the string “BEGIN RSA PUBLIC KEY”.

    The official SSHLib source only accepts private keys so I’m pretty sure my build includes your patches.

    Just wondering if you remember if there was anything else you had to do to make it work.

  4. Found the issue! It was quite simple actually….

    The ConnectBot app did not have storage access permissions. Doh. I figured it out when I saw the logs with adb logcat. EACCES error.

    It doesn’t request it on its own, probably because it’s an older version that doesn’t incorporate the request activity for the permissions? Not sure. But anyway, it works now 🙂 Thanks again for your work on this!!

Leave a Reply

Your email address will not be published. Required fields are marked *