U2F support in OpenSSH HEAD

Ron Frederick ronf at timeheart.net
Tue Dec 3 18:51:36 AEDT 2019


Hi Damien,

On Nov 14, 2019, at 3:26 PM, Damien Miller <djm at mindrot.org> wrote:
> On Fri, 1 Nov 2019, Damien Miller wrote:
>> As of this morning, OpenSSH now has experimental U2F/FIDO support, with
>> U2F being added as a new key type "sk-ecdsa-sha2-nistp256 at openssh.com"
>> or "ecdsa-sk" for short (the "sk" stands for "security key").
> 
> An update on this: I've just committed internal support for U2F/FIDO2
> security keys to OpenSSH. If ./configure can find a compatible libfido2
> then it will be used automatically, with no additional configuration
> required in OpenSSH tools. You should use libfido2 HEAD for now until
> they make their next release.
> 
> Practically, this means that you can just run "ssh-keygen -t ecdsa-sk"
> and it will work without fiddling with middleware binaries, etc.
> 
> Please give this a try - security key support is a substantial change and
> it really needs testing ahead of the next release.


I’ve been following this work with great interest, as I’ve been looking to add this support to my Python “AsyncSSH” package. I got a chance to look more closely at this over the last few days, and I’ve now got an initial implementation working and interoperating with OpenSSH-portable HEAD!

As part of my testing, I’ve been experimenting with not only plan SK keys, but also various combinations of creating certificates of SK keys signed by non-SK CA keys and vice-versa. I’ve generally been able to get OpenSSH to work with certificates of SK keys to work when signed with either non-SK or SK CAs. However, I haven’t been able to get OpenSSH to work when signing a non-SK key with an SK CA. I haven’t dug into the OpenSSH code to figure out what might be going wrong here, but I wanted to let you know about it.

These keys work fine when I use OpenSSH as a client to talk to my AsyncSSH server with this support, and AsyncSSH can use these keys successfully to talk to itself, so I think the problem is specifically in the OpenSSH server-side support for this case. Creating a certificate for an SK key signed by an SK CA works fine when OpenSSH is the server with either itself or AsyncSSH as the client. So, you’ll need to specifically try a non-SK key signed by an SK CA.


One other thing I wanted to ask you about was whether you had any plans to support SK keys as host keys. Your current PROTOCOL.u2f file says that these keys “are not used for host-based user authentication or server host key authentication”. However, I think there are a few use cases where this could make sense.

One use case is for reverse-direction connections, such as NETCONF “Call Home” described in RFC 8071. I know this isn’t something OpenSSH supports yet, but I recently added such support to AsyncSSH. See https://asyncssh.readthedocs.io/en/latest/#reverse-direction-example <https://asyncssh.readthedocs.io/en/latest/#reverse-direction-example> for more details. Basically, this use case involves a client running a process that makes an outbound TCP connection to a server but then using the resulting TCP connection once it is set up to authentication the remote server as a “user” and the client as a “host”, reversing the normal SSH direction and allowing the remote server to run commands on the local system. So, in the case of security keys, you’d create an SK server host key, and when you make the outbound connection and begin the key exchange, you’d hit the user presence button on the security key to perform the signing operation with the server host key and allow the reverse connection to be established.

A second use case would be to simply use a security key with user presence disabled as a more secure key store than just keeping a private key on the local disk of an SSH server. As long as the security key was present, the SSH server could accept new connections. However, you could remove the key at any time to disable this function, and there would be no concern about the key being remotely stolen from the server — someone would need physical access to steal the security key and need the enrollment information for that to do them any good.

The same argument can be applied to using a security key for host-based user authentication, and there you could even leave user presence enabled.


Finally, I noticed a few minor things in the PROTOCOL.u2f doc that didn’t look right. Specifically, that doc says:
> In addition to the message to be signed, the U2F signature operation
> requires a few additional parameters:
> 
> 	byte		control bits (e.g. "user presence required" flag)
> 	byte[32]	SHA256(message)
> 	byte[32]	SHA256(application)
> 	byte		key_handle length
> 	byte[]		key_handle

This isn’t really the format that these parameters are provided during a signing operation, though, at least not to the middleware library documented later in the doc. You may just want to leave this part of the description out, or at least sync it up a bit better with that later description. For instance, the key handle length is a size_t there, not a single byte, and the “control bits” is later called “flags”. The arguments are also in a different order, and there’s a missing argument which specified the key type (“alg”).
Following this you show the signed blob as:
> This signature is signed over a blob that consists of:
> 
> 	byte[32]	SHA256(application)
> 	byte		flags (including "user present", extensions present)
> 	uint32		counter
> 	byte[]		extensions
> 	byte[32]	SHA256(message)
How would the “extensions” be encoded here, though, and how would the hardware know where they end and the SHA256 of the message begins? The doc mentions an “extensions present” flag, but I don’t see that defined.
Just after this, you show the signature returned from the U2F hardware as:
> The signature returned from U2F hardware takes the following format:
> 
> 	byte		flags (including "user present")
> 	uint32		counter
> 	byte[32]	ecdsa_signature (in X9.62 format).
The signature is more than 32 bytes here, though. The middleware library returns the signature as an (r, s) pair, where each is a 32-byte string value that is later converted to integers and then encoded as a pair of MPInts. I suspect the hardware might be returning (r, s) as DER encoded in some cases and that the middleware library is hiding that, but either way the text above isn’t quite right.
Later, in the description of the sk_enroll() call, you show a “challenge” argument, but it’s not clear how that’s used. Are you doing anything with that today? I tried looking in various online docs about U2F/FIDO to see if it was described there, but I couldn’t really find anything that matched up with that. Most of what I found was much too high-level, or focused on things like the Javascript APIs in the browser and not the underlying code talking to the hardware tokens.
Thanks for all your work on this! I look forward to doing more testing on this as I fill out things like key enrollment and talking to an SSH agent process. I’ll report back here if I run into any issues with that.
-- 
Ron Frederick
ronf at timeheart.net





More information about the openssh-unix-dev mailing list