-
Notifications
You must be signed in to change notification settings - Fork 479
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
Conversation
@karel-m As promised :) |
Great job! |
And 2 warnings:
|
Cheers! :) |
fd83e8f
to
1d64c68
Compare
Fixes in, and building now... hopefully that's all acceptable. Let me know if I missed anything! :) |
gcc-7.3 with
Can we somehow avoid this warning? |
clang-tidy is without warnings 👍 |
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 Give me 5 minutes ;-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
👍
Done. I've refactored so it always returns the buffer length (i.e. string length + 1). |
6d7945d
to
6d0d58c
Compare
I added a new check in |
1522c69
to
80ff52a
Compare
OK, I've rebased to current develop branch (might need to do so again with the |
80ff52a
to
c64e4f4
Compare
There was a problem hiding this 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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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;
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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?
617993f
to
e3ec46d
Compare
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 |
@mkj did you have a look at this? |
There was a problem hiding this 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?
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. |
@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 |
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
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 |
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? |
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
05cdf36
to
4ee5bfc
Compare
Rebased to current develop branch |
Ad @mkj 's comments
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.
This is a valid point. The current all-in-one @sjaeckel can we merge this PR? (to me it looks good) |
We could put that branch of the conditional on |
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 |
Cheers! 👍 |
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