← β†’ πŸ”—

Using SSH Public Key Authentication with a Smart Card

2019-01-12

Or the result of several hours of fumbling around trying to use my new Feitian ePass Smart Card to login on my ssh server with asymmetric cryptography...

Table of Content

Surely, it can't be too hard, right?

The idea is simple: Public Key Authentication for SSH is well documented, I just want my private key to live on my hardware token instead of being a file on my hard drive.

Thanks to my job, I already had (some) experience with OpenSSL, OpenSC, PKCS#11 and PKCS#15, and it's not like these technology are new, so I was expecting it would be simply a matter of finding the right commands.

After I managed to enable the Smart Card capability of the token, I continued with the Feitian guide on how to use their product with SSH authentication...

Putty-CAC requesting a PIN

Accessing the token on Windows

Since OpenSC and OpenSSL are Unix tools, my first instinct was to use WSL (Windows Subsystem for Linux) to access the token. Unfortunately, WSL does not support libusb (yet?), meaning it's not possible.

So I installed the latest release of OpenSC for Windows and verified it worked:

>opensc-tool.exe --list-readers
# Detected readers (pcsc)
Nr.  Card  Features  Name
0    Yes             FT CCID 0

>pkcs15-tool.exe -D
Using reader with a card: FT CCID 0
PKCS#15 Card [GIDS card]:
        Version        : 2
        Serial number  : d9450332e145cd46aba5ff209bc38c
        Manufacturer ID: www.mysmartlogon.com
        Flags          :

PIN [UserPIN]
        Object Flags   : [0x3], private, modifiable
        ID             : 80
        Flags          : [0x12], local, initialized
        Length         : min_len:4, max_len:15, stored_len:0
        Pad char       : 0x00
        Reference      : 128 (0x80)
        Type           : ascii-numeric
        Tries left     : 3

Creating a new key pair

I then created a new RSA key pair:

>pkcs15-init.exe --verify-pin --auth-id 80 --generate-key rsa/2048 --key-usage sign,decrypt --label "RSA2k48"
Using reader with a card: FT CCID 0
User PIN required.
Please enter User PIN [UserPIN]:

>pkcs15-tool.exe --list-keys --list-public-keys
Using reader with a card: FT CCID 0
Private RSA Key [RSA2k48]
        Object Flags   : [0x1], private
        Usage          : [0x6], decrypt, sign
        Access Flags   : [0x1D], sensitive, alwaysSensitive, neverExtract, local
        ModLength      : 2048
        Key ref        : 129 (0x81)
        Native         : yes
        Auth ID        : 80
        ID             : 00
        MD:guid        : d18b12c0-9e85-eed3-800b-7ed6815cd351

Public RSA Key [RSA2k48]
        Object Flags   : [0x0]
        Usage          : [0x41], encrypt, verify
        Access Flags   : [0x2], extract
        ModLength      : 2048
        Key ref        : 129 (0x81)
        Native         : yes
        Path           : 3fffb081
        ID             : 00

Note: Not all key sizes are supported by all tokens. Some tokens also support some Elliptic Curves, but OpenSC's PKCS#15 tools could only create RSA keys up until recently, though I believe it's also possible to use OpenSC's PKCS#11 tools to create keys on the token...

To use this key pair with SSH, we need to export the Public part in the right format. Fortunately, there is a command to do just that:

>pkcs15-tool.exe --read-ssh-key 00
Using reader with a card: FT CCID 0
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtAIZsaRaP0rfaocuFsoZcpEhXldk2aboX3MPZAfnaHSvdp5YjfnCv8JEp0xsoDVrIE6J85rtmOt74hzOf8sc/XiCM59HpCIRKzJm9amYQfK5J+Vkcm+Rkz4AtU1R/g5PAlSDxDNj9FtdaREZu1EhPidyweoO4T1GRZxQLF9PVwRRAhps/YDb0JKKbCo7UXdXxZJeGxBidTyRfdC1EJ7xnd07Em0gGup/6K6wFz7IsZhYC2w9OLB80b0YHMs43EoFqQiHzqxNeAK90kLBCzqmfKU9tJ19qMNoO1S9gEhn9sjtbFEETJk4wF0/MqN/2i6dP8BjWbQqZOQ784TmcaG0N RSA2k48

Using the key pair with SSH

Server side

The output of the previous command can directly be added into the authorized_keys file of an OpenSSH server (located in the .ssh directory).

Note: OpenSSH requires restrictive permissions on the authorized_keys file, so in case it doesn't work, run chmod 600 authorized_keys.

Client side

On Unix systems, it should work using OpenSSH's PKCS#11 builtin support (with OpenSC installed):

$ ssh -I /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so user@remotehost
Enter PIN for 'UserPIN (GIDS card)': 

user@remotehost:~$ 

Tadaaa! Done!

Note: I did not actually test this scenario without first getting it to work on Windows by adding a certificate to the token, so in case nothing happens, read on.


It was too good to last, or "why does connecting from a Windows host have to be so hard?"

I don't typically use OpenSSH from Windows. I use PuTTY. Unfortunately, even though it seems this is a common feature request, PuTTY does not support Smart Cards πŸ™

Some forks adding support of Smart Cards exist. Some are free, some require the user to pay for a license, but none seem very trustful or up to date.

Because of that, I tried to use OpenSSH anyway.

Unfortunately, the version currently shipped with Windows does not support PKCS#11:

>ssh -I "C:\Program Files\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll" user@remotehost
no support for PKCS#11.

I tried to install a more recent binary release, but it only failed in a different way...

As I said previously, WSL is out of the question (libusb not supported), so as a last resort, I created an Ubuntu HyperV virtual machine, hoping I would be able to share an USB device with it...

Except it's apparently only supported through remote desktop on Windows VMs. πŸ˜‘

Putty-CAC

After going back to testing a few PuTTY forks, I saw the OpenSC page on SSH recommends Windows users to use Putty-CAC, so that's what I ended up doing.

Again, the documentation is sparse, so I spent a good while not understanding why it didn't work.

It turns out Putty-CAC (and all the other forks I tried) does not support authenticating with a simple Private key.

It requires a certificate with the key...

Satisfying Putty-CAC by creating a certificate from our key pair

Failing on Windows

The PKCS#15 tools we used to generate a key pair are able to store a certificate, but cannot create one.

This is a job more suited for OpenSSL.

OpenSSL commands to do that are not very user-friendly, but are relatively well documented. Unfortunately, in this case the Private key we want to use cannot leave the token, so we must tell OpenSSL how to access with it via PKCS#11.

This is normally just some arguments to add, with the path of the PKCS#11 library to use, but OpenSSL and OpenSC are Unix-first tools, and I didn't manage to get this to work on Windows...

After trying other releases of OpenSSL without getting it to output something more useful than a stack trace, I gave up and decided to use my Ubuntu dual boot...

Succeeding on Ubuntu

Required packages

Starting from the Ubuntu 18.10 Desktop Live USB.

Note: I'm not entirely sure libengine-pkcs11-openssl is required.

Some packages are in the universe repository so I first had to add it:

$ sudo add-apt-repository universe
$ sudo apt-get update
$ sudo apt-get install openssl opensc libengine-pkcs11-openssl

Once this is done, we can continue to the interesting part...

Give me the command line already!

The documentation on how to use PKCS#11 with OpenSSL sucks, and a lot of old pages show dreadful commands such as the following, supposed to be required to initialize the engine:

$ openssl engine dynamic -pre SO_PATH:/usr/lib/engines/engine_pkcs11.so \
 -pre ID:pkcs11 -pre LIST_ADD:1 -pre LOAD -pre MODULE_PATH:opensc-pkcs11.so

After failing to make the above command work for a while, and after some more googling, I found out that, at least on a modern Ubuntu system, it is actually a lot easier (for once).

So without more delay, here is the actual command I used to generate my certificate:

$ openssl req -engine pkcs11 -new -key "pkcs11:object=RSA2k48" \
 -keyform engine -out myCert.pem -days 3650 -outform pem -x509 -utf8

engine "pkcs11" set.
Enter PKCS#11 token PIN for UserPIN (GIDS card):
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----

Things of note:

  • RSA2k48 is the name of my key pair on the token
  • don't forget to set the expiration date explicitly. It's only a few months by default
  • don't forget to add -utf8 if you want to be able to set non-ascii characters in a way that works on different OSes (Windows in particular does not decode properly the default encoding)
Storing the certificate on the token

With the value of --id being the id of my existing key on the device:

$ pkcs15-init --store-certificate myCert.pem --id 00 --verify-pin
Using reader with a card: FT U2F CCID KB [CCID] 00 00
User PIN required.
Please enter User PIN [UserPIN]: 

We can verify it worked:

$ pkcs15-tool --dump
Using reader with a card: FT U2F CCID KB [CCID] 00 00
PKCS#15 Card [GIDS card]:
	Version        : 2
	Serial number  : d9450332e145cd46aba5ff209bc38c
	Manufacturer ID: www.mysmartlogon.com
	Flags          : 
PIN [UserPIN]
	Object Flags   : [0x3], private, modifiable
	ID             : 80
	Flags          : [0x12], local, initialized
	Length         : min_len:4, max_len:15, stored_len:0
	Pad char       : 0x00
	Reference      : 128 (0x80)
	Type           : ascii-numeric
	Tries left     : 3

Private RSA Key [RSA2k48]
	Object Flags   : [0x1], private
	Usage          : [0x6], decrypt, sign
	Access Flags   : [0x1D], sensitive, alwaysSensitive, neverExtract, local
	ModLength      : 2048
	Key ref        : 129 (0x81)
	Native         : yes
	Auth ID        : 80
	ID             : 00
	MD:guid        : d18b12c0-9e85-eed3-800b-7ed6815cd351

Public RSA Key [RSA2k48]
	Object Flags   : [0x0]
	Usage          : [0x41], encrypt, verify
	Access Flags   : [0x2], extract
	ModLength      : 2048
	Key ref        : 129 (0x81)
	Native         : yes
	Path           : 3fffb081
	ID             : 00

X.509 Certificate [RSA2k48]
	Object Flags   : [0x0]
	Authority      : no
	Path           : a010df24
	ID             : 00
	Encoded serial : 02 14 42026032E73F00438789443EF7DEC2A435BB2F0A

In case of mistake: replacing the certificate

On my first try, I forgot to add the -utf8 argument. This resulted in a messed-up encoding on Windows, so I had to re-generate a certificate and replace the existing one on my token.

It's supposed to be easy, but sometimes it's not:

$ pkcs15-init --update-certificate myCert.pem --id 00 --verify-pin
Using reader with a card: FT U2F CCID KB [CCID] 00 00
User PIN required.
Please enter User PIN [UserPIN]: 
Failed to update certificate: Incorrect parameters in APDU

I'm not sure why it didn't work. The --update-certificate is apparently not supported on all devices or in all cases, so I tried to simply delete the certificate before re-adding it manually:

$ pkcs15-init --delete-objects cert --id 00 --verify-pin
Using reader with a card: FT U2F CCID KB [CCID] 00 00
User PIN required.
Please enter User PIN [UserPIN]: 
Failed to delete object 0: File not found
Deleted 0 objects
Failed to delete object(s): File not found

It's not really better...

Because I was getting desperate, I tried the same command on Windows...

>pkcs15-init.exe --delete-objects cert --id 00 --verify-pin
Using reader with a card: FT CCID 0
User PIN required.
Please enter User PIN [UserPIN]: Deleted 1 objects

And it worked!!

I'm still not sure why it worked on Windows but not on Ubuntu. It's possible a bug has been fixed between OpenSC 0.18 (on Ubuntu, from the package manager) and 0.19 (on Windows, from github), but I have no certainty...

Anyway, I managed to delete the certificate on Windows, regenerated it properly on Ubuntu, and I was able to try again with Putty-CAC.

Finally getting it to work on Windows

Putty-CAC certificate selection

Putty-CAC supports two different ways to get the certificate from the token: via a PKCS#11 provider (an OpenSC dll), or via Microsoft's CryptoAPI.

PKCS#11 provider

The PKCS#11 way obviously requires a PKCS#11 library, and in our case, it's the one from OpenSC.

Using this provider requires us to select the C:\Program Files\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll file. After that, if the token is plugged-in, it should be possible to select our certificate from the selection popup.

Putty-CAC certificate selection

Microsoft CryptoAPI (CAPI) provider

I only realized after I got it to work with the PKCS#11 provider that using the Microsoft CAPI is possible, and there is a nice bonus: it doesn't require anything else to be installed (beside Putty-CAC)!

Putty-CAC requesting a PIN


Conclusion

I easily spent 5+ hours on this!

It's incredible technology that has been around from at least the 90's is still so hard to use.

Maybe hardware security token still being kind of niche can explain the lack of polish on the whole UX?

I'm not sure. But it surely won't get mainstream if it stays like that...

Anyway, in the end I didn't waste my time (too much): I can now do something actually useful with my security token. πŸ™‚

What's next?

I will have to find a practical way to keep my token around... Why is it so hard to find necklaces or similar for this purpose?

I still haven't managed to use it with KeePass (There are plugins, but I didn't find one that work with Android yet), by maybe that's for another time...

I can't backup my private key (by design).
Maybe giving up a little security by generating the key on my PC (so I can backup it) instead of directly on the device would be more sensible? For now, I don't use the token as the sole authentication method, so I would't be locked out of my systems if I lost the key, but still...

8 comments



Formatting cheat sheet.
The current page url links to a specific comment.
The comment is shown highlighted below in context.

    JavaScript is required to see the comments. Sorry...