Skip to content

LTC_ECCSIG_RFC5656 SSH+ECDSA signature format #438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 13, 2018

Conversation

rmw42
Copy link
Contributor

@rmw42 rmw42 commented Oct 2, 2018

Implementation of SSH+ECDSA signature format for ECC signatures as defined in RFC5656.

Includes a SSH data model encode/decode (gated on #define LTC_SSH), and OID string<->Curve comparison helper function (also used for secp256k1 checks for LTC_ECCSIG_ETH27)

Checklist

  • documentation is added or updated
  • tests are added or updated

@rmw42
Copy link
Contributor Author

rmw42 commented Oct 3, 2018

@karel-m As promised :)

@karel-m
Copy link
Member

karel-m commented Oct 3, 2018

Great job!

@karel-m
Copy link
Member

karel-m commented Oct 3, 2018

And 2 warnings:

src/misc/ssh/ssh_test.c:59:5: error: no previous prototype for ‘_ssh_encoding_test’ [-Werror=missing-prototypes]
 int _ssh_encoding_test(void)
     ^~~~~~~~~~~~~~~~~~
src/misc/ssh/ssh_test.c:154:5: error: no previous prototype for ‘_ssh_decoding_test’ [-Werror=missing-prototypes]
 int _ssh_decoding_test(void)
     ^~~~~~~~~~~~~~~~~~

@rmw42
Copy link
Contributor Author

rmw42 commented Oct 3, 2018

Great job!

Cheers! :)

@rmw42 rmw42 force-pushed the feature/ssh-ecdsa branch from fd83e8f to 1d64c68 Compare October 3, 2018 19:23
@rmw42
Copy link
Contributor Author

rmw42 commented Oct 3, 2018

Fixes in, and building now... hopefully that's all acceptable. Let me know if I missed anything! :)

@karel-m
Copy link
Member

karel-m commented Oct 3, 2018

gcc-7.3 with -Wformat=2

src/pk/ecc/ecc_ssh_ecdsa_encode_name.c: In function ‘ecc_ssh_ecdsa_encode_name’:
src/pk/ecc/ecc_ssh_ecdsa_encode_name.c:47:4: error: format not a string literal, argument types not checked [-Werror=format-nonliteral]
    size = snprintf(buffer, *buflen, pattern, oidstr);
    ^~~~

Can we somehow avoid this warning?

@karel-m
Copy link
Member

karel-m commented Oct 3, 2018

clang-tidy is without warnings 👍

@rmw42
Copy link
Contributor Author

rmw42 commented Oct 4, 2018

gcc-7.3 with -Wformat=2

src/pk/ecc/ecc_ssh_ecdsa_encode_name.c: In function ‘ecc_ssh_ecdsa_encode_name’:
src/pk/ecc/ecc_ssh_ecdsa_encode_name.c:47:4: error: format not a string literal, argument types not checked [-Werror=format-nonliteral]
    size = snprintf(buffer, *buflen, pattern, oidstr);
    ^~~~

Can we somehow avoid this warning?

Ah, damn, an I thought I was being so clever changing the format string... well, if it has to be a literal, I can get rid of pattern entirely, use four snprintf() calls in the different branches of the if but leave the buffer length check as-is at the end...

Give me 5 minutes ;-)

Copy link
Member

@sjaeckel sjaeckel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

👍

@rmw42
Copy link
Contributor Author

rmw42 commented Oct 4, 2018

in case of CRYPT_BUFFER_OVERFLOW we should return required buffer length

Done. I've refactored so it always returns the buffer length (i.e. string length + 1).

@rmw42 rmw42 force-pushed the feature/ssh-ecdsa branch 2 times, most recently from 6d7945d to 6d0d58c Compare October 4, 2018 21:19
@rmw42
Copy link
Contributor Author

rmw42 commented Oct 5, 2018

I added a new check in ssh_decode_sequence_multi to ensure we don't read past the end of the buffer, which caught a bug in the name-list decoding tests (I'd used sizeof(string) instead of sizeof(nlist3) - copy/paste bug).

@rmw42 rmw42 force-pushed the feature/ssh-ecdsa branch 2 times, most recently from 1522c69 to 80ff52a Compare October 5, 2018 09:51
@rmw42
Copy link
Contributor Author

rmw42 commented Oct 5, 2018

OK, I've rebased to current develop branch (might need to do so again with the ecc_verify_hash_ex reversion), and I think that's all the things we've discussed... if there are any more issues, please let me know 😄

@rmw42 rmw42 force-pushed the feature/ssh-ecdsa branch from 80ff52a to c64e4f4 Compare October 5, 2018 10:39
@karel-m karel-m mentioned this pull request Oct 6, 2018
4 tasks
Copy link
Member

@sjaeckel sjaeckel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for more nitpicks, minor comments, but there's also one important thing I only spotted now (regarding the length-checks when decoding)

}

/* Check we've not read too far */
if (in > sentinel) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this kind of check be in each case where we read more than 1 byte?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, but it would make the implementation a lot uglier. It'd mean we couldn't use the LOAD32H macros as they don't do checks after each byte.

The main reason I could see for doing that is that there might be information leaked if it can read past the end of the buffer... but only by someone with the ability to change the code, who could read from that memory anyway.

It's in the loop, so it will be checked after each object read, whether that's 4 or 8 or however many bytes. Personally, I think that's OK, but I'm happy to move it if you disagree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would make the implementation a lot uglier

sure, but it's required IMO as the following isn't the only bad thing that could happen IMO

The main reason I could see for doing that is that there might be information leaked if it can read past the end of the buffer...

... but if you put the buffer at the end of a page it could also segfault or not?

And isn't it even UB to read past an allocated buffer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the same would apply to anything that reads from a buffer if the specified length is greater than the size of the buffer... it shouldn't segfault unless someone has done something weird with the virtual memory page protection. Even if it does, if an application asks us to read 1,000,000 bytes from a 1,000 byte buffer, or passes us a pointer which cannot be read from at all, is a segfault not an appropriate response?

Instead of a sentinel at the end, I could keep track of the bytes available to read, and put a second switch-case in the loop to calculate the size which will be read? That's still going to be somewhat delicate around the variable-length string/mpint cases...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I've ended up with three switch-cases:

  • the length field of the object (if needed)
  • read/calculate the length of the object
  • read the object itself

Should now be impossible for ssh_decode_sequence_multi to read beyond the end of the input.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is a segfault not an appropriate response?

sure it is and if we can prevent it by properly checking indicators like the required vs available length we should do it IMO.

Instead of a sentinel at the end, I could keep track of the bytes available to read, and put a second switch-case in the loop to calculate the size which will be read? That's still going to be somewhat delicate around the variable-length string/mpint cases...

how about

diff --git a/src/misc/ssh/ssh_decode_sequence_multi.c b/src/misc/ssh/ssh_decode_sequence_multi.c
index 2375df8..aa171b3 100644
--- a/src/misc/ssh/ssh_decode_sequence_multi.c
+++ b/src/misc/ssh/ssh_decode_sequence_multi.c
@@ -37,14 +37,21 @@ int ssh_decode_sequence_multi(const unsigned char *in, unsigned long inlen, ...)
    unsigned long size, bufsize;
+   long len;
 
    LTC_ARGCHK(in    != NULL);
+   LTC_ARGCHK(inlen <= LONG_MAX);
 
    /* Decode values from buffer */
+   len = inlen;
    va_start(args, inlen);
    while ((type = (ssh_data_type)va_arg(args, int)) != LTC_SSHDATA_EOL) {
+      err = CRYPT_BUFFER_OVERFLOW;
       switch (type) {
          case LTC_SSHDATA_BYTE:
+            if (len < 1) goto error;
             cdata = va_arg(args, unsigned char*);
             *cdata = *in++;
+            len--;
             break;
          case LTC_SSHDATA_BOOLEAN:
+            if (len < 1) goto error;
             cdata = va_arg(args, unsigned char*);
@@ -55,4 +62,6 @@ int ssh_decode_sequence_multi(const unsigned char *in, unsigned long inlen, ...)
             *cdata = (*in++)?1:0;
+            len--;
             break;
          case LTC_SSHDATA_UINT32:
+            if (len < 4) goto error;
             u32data = va_arg(args, ulong32*);
@@ -60,4 +69,6 @@ int ssh_decode_sequence_multi(const unsigned char *in, unsigned long inlen, ...)
             in += 4;
+            len -= 4;
             break;
          case LTC_SSHDATA_UINT64:
+            if (len < 8) goto error;
             u64data = va_arg(args, ulong64*);
@@ -65,2 +76,3 @@ int ssh_decode_sequence_multi(const unsigned char *in, unsigned long inlen, ...)
             in += 8;
+            len -= 8;
             break;
@@ -73,6 +85,3 @@ int ssh_decode_sequence_multi(const unsigned char *in, unsigned long inlen, ...)
             if (size > 0) {
-               if (size >= bufsize) {
-                  err = CRYPT_BUFFER_OVERFLOW;
-                  goto error;
-               }
+               if (size >= bufsize) goto error;
                XSTRNCPY(sdata, (const char *)in, size);
@@ -83,4 +92,6 @@ int ssh_decode_sequence_multi(const unsigned char *in, unsigned long inlen, ...)
             in += size;
+            len -= size;
             break;
          case LTC_SSHDATA_MPINT:
+            if (len < 4) goto error;
             vdata = va_arg(args, void*);
@@ -91,2 +102,3 @@ int ssh_decode_sequence_multi(const unsigned char *in, unsigned long inlen, ...)
             } else {
+               if (len < size) goto error;
                if ((err = mp_read_unsigned_bin(vdata, (unsigned char *)in, size)) != CRYPT_OK)          { goto error; }
@@ -94,2 +106,3 @@ int ssh_decode_sequence_multi(const unsigned char *in, unsigned long inlen, ...)
             in += size;
+            len -= size;
             break;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that'd work too, but some of the variable-length ones would need extra checks.

How's the triple-switch version? It's a bit longer but maybe clearer intent...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How's the triple-switch version?

I'm not too much a fan of it, @karel-m your thoughts?

@rmw42 rmw42 force-pushed the feature/ssh-ecdsa branch from 617993f to e3ec46d Compare October 8, 2018 07:59
@rmw42
Copy link
Contributor Author

rmw42 commented Oct 8, 2018

I've updated the length-check to remove one of the switches and keep track of the length remaining, rather than the end pointer, and rebased to the develop branch to fix the merge issues around cherry-picking pk_oid_cmp_with_ulong.

@sjaeckel
Copy link
Member

sjaeckel commented Oct 8, 2018

@mkj did you have a look at this?

Copy link
Collaborator

@mkj mkj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks OK, couple of small things I've noted.

Unrelated to the patch, I'm curious where SSH signature encoding is being used externally to the SSH protocol?

@rmw42
Copy link
Contributor Author

rmw42 commented Oct 8, 2018

Unrelated to the patch, I'm curious where SSH signature encoding is being used externally to the SSH protocol?

I don't think it is used anywhere else... @karel-m asked for this so you could avoid having to transform another signature format in Dropbear.

@karel-m
Copy link
Member

karel-m commented Oct 9, 2018

@mkj the idea was mine. During my (unfinished) work - karel-m/dropbear@4530ff6 - on a PR adopting Dropbear to the new libtomcrypt ecc_* stuff I saw the dark magic in dropbear's ecc.c related to ECDSA signatures which lead me to the idea that libtomcrypt should do a better job supporting our "prominent customer" (= dropbear).

@mkj
Copy link
Collaborator

mkj commented Oct 9, 2018

Ah right. I'm not sure it really makes sense for libtomcrypt to be parsing SSH packets when it's alongside another parser anyway.

I think it would work fine if there were just a

int ecc_verify_hash_raw(void *r, void *s,
    const unsigned char *hash, unsigned long hashlen,
    int *stat, const ecc_key *key)

etc which could be called? Dropbear needs to do some work outside anyway to know which hash_desc to use.

Currently Dropbear's bundled libtomcrypt skips LTC_DER since it isn't necessary (I just treat them as opaque blobs in a few places) - it avoids some code size. It would be nice to avoid having to pull that in too, maybe the dropbear_ecc_curve.oid in the new branch should just be a long array to memcmp()?

@sjaeckel
Copy link
Member

sjaeckel commented Oct 9, 2018

maybe the dropbear_ecc_curve.oid in the new branch should just be a long array to memcmp()?

The plan is to make all OID appearances per default string-based (providing still the possibility to use the ulong-array approach to de-/encode via the DER API). Would that be fine for you as well?

rmw42 added 4 commits October 12, 2018 10:22
Wrap signature format in #ifdef LTC_SSH
Update docs
Code review fixes
Replace strcmp/memcmp with XSTRCMP/XMEMCMP for check-source
Fix for check-defines
XSTRCMP/XMEMCMP != 0
GCC7.3 wants only literal strings for sprintf format
Code review changes
Rework SSH decoding and tests
Fix encoding and tests
COMPARE_TESTVECTOR macro
Single return point in ssh_decode_sequence_multi
Actually use XSTRNCPY rather than just defining it
More code review fixes
Code review tweaks
Ensure it's not possible to read past buffer end
Keep track of size remaining, not end pointer
@rmw42 rmw42 force-pushed the feature/ssh-ecdsa branch from 05cdf36 to 4ee5bfc Compare October 12, 2018 09:23
@rmw42
Copy link
Contributor Author

rmw42 commented Oct 12, 2018

Rebased to current develop branch

@karel-m
Copy link
Member

karel-m commented Oct 13, 2018

Ad @mkj 's comments

I think it would work fine if there were just a int ecc_verify_hash_raw(void *r, void *s, ...)

I do not like this. This will expose internal libtomcrypt guts (pointers to bignum structures) which we do not have yet in any public API function. Plus there is of course a completely new bunch of troubles related to the fact that math provider is not always libtommath.

Currently Dropbear's bundled libtomcrypt skips LTC_DER since it isn't necessary...

This is a valid point. The current all-in-one ecc_sign_hash_ex and ecc_verify_hash_ex forces anyone using ECDSA to link the whole ASN.1 stuff to their binary even if they are using just RFC 7518 or RFC 5656 (ssh2) signature format. IMO it is worth rethinking in a separate PR.

@sjaeckel can we merge this PR? (to me it looks good)

@rmw42
Copy link
Contributor Author

rmw42 commented Oct 13, 2018

The current all-in-one ecc_sign_hash_ex and ecc_verify_hash_ex forces anyone using ECDSA to link the whole ASN.1 stuff to their binary

We could put that branch of the conditional on #ifdef LTC_DER so LTC_ECCSIG_ANSIX962 is only available as a signature format if the library is built with DER support and returns an error otherwise.

@karel-m
Copy link
Member

karel-m commented Oct 13, 2018

This will not handle a static linking scenario when you have a static library built with all ASN.1/DER support but you want to just use RFC 7518 ECDSA signatures. I think we should somehow split ecc_sign_hash_ex.c and ecc_verify_hash_ex.c into more .c files.

@rmw42
Copy link
Contributor Author

rmw42 commented Oct 14, 2018

Cheers! 👍

@rmw42 rmw42 deleted the feature/ssh-ecdsa branch October 14, 2018 07:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants