double length prefix in ssh-keygen certificates (values of critical options)

Dmitry Savintsev dsavints at gmail.com
Thu Apr 23 19:22:44 AEST 2015


Hi,

I have a question regarding the binary format of the certificates generated
with ssh-keygen, in particular when the critical options of source-address
or force-command are present and the correspondence to the certificate
format specifications such as
http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
.

It appears that the string values of the source-address and force-command
are prepended with *two* length offsets - 4-byte offset with the integer
value of len(string)+4 followed by the 4-byte offset with the proper
length, and then the string.  Is it a correct behavior?  I could not find
anything in the spec that would prescribe such double-prefixing, or any
description of why of all the strings it is done only for the values of
critical options (not the labels etc.)   The "Critical Options" section of
the PROTOCOL.certkeys (referenced above) says only the following about the
format for those options:
   string       name
   string       data

so I would expect the "normal" string serialization for both name (label)
and the data (actual value).  There is also no list or multiple string
values involved - both the source-address and force-command are singe
"flat" strings (source-address can have multiple IPs but they are
comma-separated inside of the same string).

Could it be a bug in ssh-keygen?

When I generate certificates that include such options - for example, with
"ssh-keygen -s ... -O source-address=10.78.72.0/29 -O
force-command=/tmp/foobar" and then decode the generated certificate (awk
'{print $2}' filename-cert.pub | base64 -D | hexdump -C )
I get the following relevant snippet of the dump:

00000190  00 00 00 27 00 00 00 0e  73 6f 75 72 63 65 2d 61
 |...'....source-a|
000001a0  64 64 72 65 73 73 *00 00  00 11 00 00 00 0d *31 30
 |ddress........10|
000001b0  2e 37 38 2e 37 32 2e 30  2f 32 39 00 00 00 82 00
 |.78.72.0/29.....|

highlighted is the double-prefix in question: 00 00  00 11 00 00 00 0d

The same happens with the force-command value.

This apparent deviation (unless I misread the spec, of course!) creates
problems in terms of interoperability with other tools.  Go ssh library (
https://godoc.org/golang.org/x/crypto/ssh), for example, does not do the
"double-wrapping", and as a result, you cannot read the certificates
generated with Go using ssh-keygen -L -f <filename>.  ssh-keygen tries to
read the first 4 bytes of the string value as the second length offset and
of course things quickly go south. Here's a hexdump of the certificate
generated with Go around the critical options section:
00000180  00 00 00 00 00 00 32 00  00 00 00 00 00 00 64 00
 |......2.......d.|
00000190  00 00 43 00 00 00 0d 66  6f 72 63 65 2d 63 6f 6d
 |..C....force-com|
000001a0  6d 61 6e 64 *00 00 00 0b*  2f 74 6d 70 2f 66 6f 6f
 |mand..../tmp/foo|
000001b0  62 61 72 00 00 00 0e 73  6f 75 72 63 65 2d 61 64
 |bar....source-ad|
000001c0  64 72 65 73 73 00 00 00  0d 31 30 2e 37 38 2e 37
 |dress....10.78.7|
000001d0  32 2e 30 2f 32 39 00 00  00 16 00 00 00 0e 70 65
 |2.0/29........pe|

- so before the value of force-command, there is a single length offset 00
00 00 0b, and before the IP address - a single length offset: 00 00 00  0d

ssh-keygen -L on such a Go-generated certificate gives the following error:
Critical Options:
buffer_get_string_ret: bad string length 796159344
buffer_get_string: buffer error

The "bad string length" is easily explained  - the decimal 796159344 is
0x2f746d70 which comes from the bytes 2f 74 6d 70 - "*/tmp*" in the
"/tmp/foobar" string value of the force-command critical option.  If I hack
the Go ssh library to add an extra prefix with length+4 value, then
ssh-keygen -L is happy again.

Let me know if you agree that it is a bug in ssh-keygen, I'll be happy to
open a Bugzilla ticket.

Thanks,

Dmitry


More information about the openssh-unix-dev mailing list