Using SSH Public Key Authentication with a Smart Card
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?
- It was too good to last, or "why does connecting from a Windows host have to be so hard?"
- Satisfying Putty-CAC by creating a certificate from our key pair
- Finally getting it to work on Windows
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...
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
The output of the previous command can directly be added into the
authorized_keys file of an OpenSSH server (located in the
Note: OpenSSH requires restrictive permissions on the
authorized_keys file, so in case it doesn't work, run
chmod 600 authorized_keys.
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:~$
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. 😑
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
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:
RSA2k48is 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
-utf8if 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
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 certitude...
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 supports two different ways to get the certificate from the token: via a PKCS#11 provider (an OpenSC dll), or via Microsoft's CryptoAPI.
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.
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)!
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. 🙂
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...