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

Dmitry Savintsev dsavints at gmail.com
Thu Apr 23 23:10:59 AEST 2015


I believe the double-prefixing in ssh-keygen.c in the add_string_option
function:

cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/ssh-keygen.c?annotate=1.269
                   1453: static void
                   1454: add_string_option(struct sshbuf *c, const char
*name, const char *value)
                   1455: {
                   1456:        struct sshbuf *b;
                   1457:        int r;
                   1458:
                   1459:        debug3("%s: %s=%s", __func__, name, value);
                   1460:        if ((b = sshbuf_new()) == NULL)
                   1461:                fatal("%s: sshbuf_new failed",
__func__);
                   1462:        if ((r = *sshbuf_put_cstring(b, value))* !=
0 ||
                   1463:            (r = sshbuf_put_cstring(c, name)) != 0
||
                   1464:            (r = *sshbuf_put_stringb(c, b)*) != 0)
                   1465:                fatal("%s: buffer error: %s",
__func__, ssh_err(r));

First time the value is length-prefixed with the sshbuf_put_cstring call
(line 1462), and then the result ("b") is sent to sshbuf_put_stringb (line
1464) which treats b (that already has the length prefix) as a string and
prepends the second length field.

The unwrapping of the double length prefix is done in the show_options
function (from line 1795 of ssh-keygen.c).  The first length prefix is
"eaten up" by the sshbuf_froms(options, &option) call on line 1807 in:

                   1806:                if ((r =
sshbuf_get_cstring(options, &name, NULL)) != 0 ||
                   1807:                    (r = *sshbuf_froms(options,
&option)*) != 0)

and the second one processed by sshbuf_get_cstring(option, &arg, NULL) a
few lines later (notice that the option structure that was the destination
on line 1807 becomes the source on line 1820):
                   1817:                else if ((v00 || in_critical) &&
                   1818:                    (strcmp(name, "force-command")
== 0 ||
                   1819:                    strcmp(name, "source-address")
== 0)) {
                  1820:                        if ((r =
*sshbuf_get_cstring(option,
&arg, NULL)*) != 0)
                   1821:                                fatal("%s: buffer
error: %s",
                   1822:                                    __func__,
ssh_err(r));
                   1823:                        printf(" %s\n", arg);


On Thu, Apr 23, 2015 at 11:22 AM, Dmitry Savintsev <dsavints at gmail.com>
wrote:

> 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