LTC_ECCSIG_RFC5656 SSH+ECDSA signature format #438
Labels
No labels
bug
duplicate
enhancement
feature
help wanted
improve API
invalid
needs documentation
not-planned
rtfm
stale
support
wishlist
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-libtom/libtomcrypt!438
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/ssh-ecdsa"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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
@karel-m As promised :)
Please move this part to
tomcrypt_private.hPlease move
src/misc/ssh/ssh_test.ctotests/ssh_test.c(+ do not forget to run./updatemakes.sh)Once we move this stuff to
tests/ssh_test.cthere is no need for#ifndef LTC_TESTIsn't it too small? I am for
oid[64].I prefer to use explicit:
XSTRCMP(x, y) != 0orXSTRCMP(x, y) == 0(applies also to other places)@ -0,0 +1,70 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisMaybe rather
buflen <= 20- according toman 3 strcpy: Thestrcpy()function copies the string pointed to by src, including the terminating null byte.buflen <= 20buflen <= 20I thing it should be
nistp521Perhaps
if (buflen <= strlen(oidstr)+12)orif (buflen <= oidlen + 12)- sprintf AFAIK writes terminating nul byteIsn't it too small? I am for oidstr[64].
To keep the style used on other places in libtomcrypt it should be
unsigned long *buflen(but as it is a private function maybe we can leave it as it is, @sjaeckel ?)Great job!
And 2 warnings:
static int _ssh_encoding_test(void)static int _ssh_decoding_test(void)if (XMEMCMP(buffer, uint32, buflen) != 0)+ all similar places belowand clang-tidy complains here:
So maybe change it to:
if (XSTRCMP(oidstr, oid) == 0) return CRYPT_OK;without
else@ -0,0 +1,70 @@/* LibTomCrypt, modular cryptographic library -- Tom St Denisclang-tidy complains here (+ few other places in this file):
So perhaps use something else than
strcpye.g.strncpy/XMEMCPYOK
OK
If an OID can be 64 chars then the SSH 64-char limit on the "ecdsa-sha2-XXXX" string would need to be higher. Happy to change it if there are super-long OIDs out there.
Will do. And I'll go with the "goto error / single return" pattern so clang-tidy is happy.
@ -0,0 +1,70 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisYeah... my fingers are too familiar with powers of 2 ^_^;;
Cheers! :)
OK
@ -0,0 +1,70 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisYeah, the string is 19 chars long, so we need 19+1 to store it... if the buffer is 19 or fewer, it's too small. I figured "<= 19" was slightly clearer than "< 20" or "< 19 + 1", but none are particularly great.
What about:
Done.
@ -0,0 +1,70 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisCompletely rewritten to use snprintf() and not need any hand-counted string lengths
@ -0,0 +1,70 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisReplaced with snprintf() and combined with buffer length checking, so no more magic numbers.
@ -0,0 +1,70 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisDone, and it writes out the length into *buflen.
Changed to 64
@ -0,0 +1,70 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisChanged to 64
Fixes in, and building now... hopefully that's all acceptable. Let me know if I missed anything! :)
gcc-7.3 with
-Wformat=2Can 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
patternentirely, use foursnprintf()calls in the different branches of theifbut leave the buffer length check as-is at the end...Give me 5 minutes ;-)
Looks good!
👍
please don't make this public API or do you see a future requirement from a user PoV that this is needed?
If you agree, please remove this part and move the function declaration from
tomcrypt_pk.htotomcrypt_private.h@ -155,2 +155,4 @@#endif /* LTC_PADDING */#ifdef LTC_SSHtypedef enum ssh_data_type_ {This should go totomcrypt_private.hOh no, it shouldn't.
@ -157,0 +167,4 @@} ssh_data_type;/* VA list handy helpers with tuples of <type, data> */int ssh_encode_sequence_multi(unsigned char *out, unsigned long *outlen, ...);instead this should probably be as follows?
int ssh_encode_sequence_multi(unsigned char *out, unsigned long *outlen, int type, void* data, ...);not sure though if it would clutter the implementation too much!?
(same applies to
ssh_decode_sequence_multi())@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St Denisstring terminator is
'\0'same
I doubt this works on all architectures *cough* ;)
First I thought why we can't use
strlcpy()... then I remembered this standardization thingy... as we stay with this please introduce a macroXSTRNCPYintomcrypt_custom.h(and also add it to the list incrypt.texandhelper.plplease)can't we use
LOAD32H()here?The same applies to all the other cases where we load some
unsigned long(useLOAD64Hwhere appropriate) resp. we could useSTORE{32,64}H()when storing those values.Btw. we also have types for exactly that purpose,
ulong32andulong64(seetomcrypt_cfg.hfor details)why is a byte stored as
unsigned long?I just realized that theoops ... ltm isn't released yet with those changes... *cough* https://github.com/libtom/libtommath/issues/122 *cough*srcargument inunsigned_read()intomcrypt_math.hcould be changed toconst unsigned char *srcas both ltm and tfm nowadays haveconst*arguments where applicable@ -0,0 +110,4 @@STORE32H(u32data, out);out += 4;break;case LTC_SSHDATA_UINT64:c.f. comment regarding
LTC_SSHDATA_UINT64inssh_decode_sequence_multi()@ -37,6 +37,93 @@ static unsigned int sizes[] = {#endifstaticand a more meaningful name please@ -0,0 +1,310 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisYou can also use
DO(do_compare_testvector(..., this prints some more information on why it failed.... we could probably also add a macro ...
I put it in the same places as
ecc_get_oid_str(), but this probably shouldn't be public - it's unlikely that a user application will have created a key but not know what type it is, so this only really makes sense for internal checks.Will move it and remove the docs.
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisOK
@ -37,6 +37,93 @@ static unsigned int sizes[] = {#endifThat's what it was called when it was function-local ;-)
I'll rename it to
curvenamesand make it static.@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisManaged to outthink myself considering register passing conventions/what this would be on the 'stack'/that it's a cast of
void *when encoding... basically being silly. I'll make it something sane - uint8?Done. I've refactored so it always returns the buffer length (i.e. string length + 1).
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisAh, those were exactly what I was looking for. Cheers! Fixing now :)
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisAwesome. That's much tidier now :)
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisDone. I've also added a check for
strcpyinhelper.pl- not used anywhere in the code as far as I can tell, but presumably it should be alerted if someone introduces it.@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St Denisunsigned charthen@ -157,0 +167,4 @@} ssh_data_type;/* VA list handy helpers with tuples of <type, data> */int ssh_encode_sequence_multi(unsigned char *out, unsigned long *outlen, ...);Not sure how well va_args lists handle restarting in the middle of the fixed parameters?
I'm going to alter the implementation so we don't have
void *databeing cast all over the place but explicitly collect the correct type from the stack.@ -157,0 +167,4 @@} ssh_data_type;/* VA list handy helpers with tuples of <type, data> */int ssh_encode_sequence_multi(unsigned char *out, unsigned long *outlen, ...);how about something like:
[edit]: FYI yes, that's an incomplete patch ;)
@ -157,0 +167,4 @@} ssh_data_type;/* VA list handy helpers with tuples of <type, data> */int ssh_encode_sequence_multi(unsigned char *out, unsigned long *outlen, ...);I've gone for something a bit more radical... just updating the tests to cover byte/boolean/uint64 now. Let me know what you think, I can always change it if you're not happy.
@ -0,0 +1,310 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisDone
@ -53,6 +53,8 @@ sub check_source {push @{$troubles->{unwanted_memmove}}, $lineno if $file =~ /^src\/.*\.c$/ && $l =~ /\bmemmove\s*\(/;push @{$troubles->{unwanted_memcmp}}, $lineno if $file =~ /^src\/.*\.c$/ && $l =~ /\bmemcmp\s*\(/;push @{$troubles->{unwanted_strcmp}}, $lineno if $file =~ /^src\/.*\.c$/ && $l =~ /\bstrcmp\s*\(/;push @{$troubles->{unwanted_strcpy}}, $lineno if $file =~ /^src\/.*\.c$/ && $l =~ /\bstrcpy\s*\(/;👍
@ -157,0 +167,4 @@} ssh_data_type;/* VA list handy helpers with tuples of <type, data> */int ssh_encode_sequence_multi(unsigned char *out, unsigned long *outlen, ...);Changed to a single return path and re-pushed.
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisTBH I prefer a switch-case over a long else-if chain for two reasons:
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisI was mostly concerned about the need for a bunch of variable definitions at the top of the function which would each only be used by a single case. What about switch-case with braced code blocks? e.g.:
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisI don't have a problem with both, but it's true - the braced code blocks worsen readability again
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisI guess there's not going to be any way to make it pretty :(
Switch-case with variable definitions at the top of the loop, then? And I can put
typeinto the formal arg list, change thefortowhile (type != LTC_SSHDATA_EOL)and put the reading oftypeat the end of the block. Sound good?Any other changes I should make after that?
@ -157,0 +167,4 @@} ssh_data_type;/* VA list handy helpers with tuples of <type, data> */int ssh_encode_sequence_multi(unsigned char *out, unsigned long *outlen, ...);Ah...
warning: second argument to 'va_start' is not the last named parameter [-Wvarargs]. Since it has to go through the loop twice,typecan't be in the formal parameters forssh_encode_sequence_multi...@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St Denisunfortunately not :/
I'd say at the top of the function, not the loop
why not change the signature to the proposed
int ssh_decode_sequence_multi(const unsigned char *in, unsigned long inlen, int type, void *data, ...)?Should it really be a valid case to call
ssh_decode_sequence_multi(foo, bar, LTC_SSHDATA_EOL)?@ -157,0 +167,4 @@} ssh_data_type;/* VA list handy helpers with tuples of <type, data> */int ssh_encode_sequence_multi(unsigned char *out, unsigned long *outlen, ...);I guess you forgot to update the
va_start()argument...this compiles w/o warning and tests are OK
(please don't call it
type_;) )@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisI can't put
typeinto the formal parameters because the encoding needs to go through the list twice, and can't beva_started from the middle of the formal parameters, so it would make the signatures of the two methods different :(And
datashouldn't really go in there because it then forces things to bevoid*pointers even when they should be integers :(@ -157,0 +167,4 @@} ssh_data_type;/* VA list handy helpers with tuples of <type, data> */int ssh_encode_sequence_multi(unsigned char *out, unsigned long *outlen, ...);That's different to how
der_encode_sequence_multietc handle varargs, but I can do it that way if you want?@ -157,0 +167,4 @@} ssh_data_type;/* VA list handy helpers with tuples of <type, data> */int ssh_encode_sequence_multi(unsigned char *out, unsigned long *outlen, ...);OK, leave the signatures as they are, sorry for the noise :)
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisBoth valid points I didn't see!
Again, sorry for the noise!
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisBut please, still change back to switch-case :)
@ -157,0 +167,4 @@} ssh_data_type;/* VA list handy helpers with tuples of <type, data> */int ssh_encode_sequence_multi(unsigned char *out, unsigned long *outlen, ...);No worries... it's annoying that there's no elegant solution, it's like an itch in the brain! :)
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisDone, and I've moved the params to the top of the function.
I added a new check in
ssh_decode_sequence_multito ensure we don't read past the end of the buffer, which caught a bug in the name-list decoding tests (I'd usedsizeof(string)instead ofsizeof(nlist3)- copy/paste bug).OK, I've rebased to current develop branch (might need to do so again with the
ecc_verify_hash_exreversion), and I think that's all the things we've discussed... if there are any more issues, please let me know 😄Sorry for more nitpicks, minor comments, but there's also one important thing I only spotted now (regarding the length-checks when decoding)
@ -154,6 +154,23 @@ int padding_pad(unsigned char *data, unsigned long length, unsigned long* paddedint padding_depad(const unsigned char *data, unsigned long *length, unsigned long mode);oops, just found that this doesn't belong here anymore but in
tests/tomcrypt_test.h@ -251,7 +251,9 @@ typedef enum ecc_signature_type_ {/* raw R, S values */I'm not 100% sure if this shouldn't be within
#ifdef LTC_SSH?if we're already at it, can you please directly add a
,at the end :)@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St Denis❤️
shouldn't this kind of check be in each case where we read more than 1 byte?
@ -0,0 +82,4 @@/* Check we have sufficient space */if (*outlen < size) {*outlen = size;err = CRYPT_BUFFER_OVERFLOW;didn't we say we should return the required size in this error-case?
if we merge this after #404 there's going to be
int pk_oid_cmp_with_ulong(const char *o1, const unsigned long *o2, unsigned long o2size);;)that define is only required once after the
#endif@ -154,6 +154,23 @@ int padding_pad(unsigned char *data, unsigned long length, unsigned long* paddedint padding_depad(const unsigned char *data, unsigned long *length, unsigned long mode);Moved.
@ -251,7 +251,9 @@ typedef enum ecc_signature_type_ {/* raw R, S values */I think it makes sense to define the signature format in all cases, whether or not the implementation is built into the library. If
LTC_SSHisn't defined, then it'll fall through to the "unknown format" errors in the functions.I think a trailing comma in an enum results in a compiler warning. Not sure, though?
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisProbably, but it would make the implementation a lot uglier. It'd mean we couldn't use the
LOAD32Hmacros 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.
@ -0,0 +82,4 @@/* Check we have sufficient space */if (*outlen < size) {*outlen = size;err = CRYPT_BUFFER_OVERFLOW;Oops. Changed another case (
ecc_ssh_decode_name) but missed this one. Fixing it now.I can drop
ecc_cmp_oid_strentirely, then. Turned out that it was only needed for the secp256k1 checks forLTC_ECCSIG_ETH27. Let me know if we want to do that.OK
@ -251,7 +251,9 @@ typedef enum ecc_signature_type_ {/* raw R, S values */agreed!
shouldn't on modern compilers :) (e.g. the
ltc_asn1_XXenums have it as well)@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St Denissure, but it's required IMO as the following isn't the only bad thing that could happen IMO
... 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?
We keep it for now as is I'd say as the other PR seems to take some more iterations as it's not entirely ready yet.
Even better, simply cherry-pick those commits and whoever gets merged first will have them and the second will be rebased and the commits removed :) Git FTW
c0407783ac383f9509a62dde99de4b6606c4d4f7
c81e1cb1dc8b36286233605c3a10c47584ec0e79
6abfab4d03389ccfe2c31f7ead84c706befc3e60
@ -251,7 +251,9 @@ typedef enum ecc_signature_type_ {/* raw R, S values */Yup. I thought I'd seen this somewhere recently, but apparently not - it compiles cleanly.
Took a while to figure out the syntax - thought I'd have to name the branch I was pulling from - but that was remarkably painless! 🍒
I'll get rid of
ecc_cmp_oid_strnow.@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisWell, 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...
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisOK, I've ended up with three switch-cases:
Should now be impossible for
ssh_decode_sequence_multito read beyond the end of the input.@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St Denissure it is and if we can prevent it by properly checking indicators like the required vs available length we should do it IMO.
how about
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisYeah, 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...
@ -0,0 +1,161 @@/* LibTomCrypt, modular cryptographic library -- Tom St DenisI'm not too much a fan of it, @karel-m your thoughts?
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
developbranch to fix the merge issues around cherry-pickingpk_oid_cmp_with_ulong.@mkj did you have a look at this?
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?
@ -0,0 +123,4 @@XSTRNCPY(sdata, (const char *)in, size);sdata[size] = '\0'; /* strncpy doesn't NUL-terminate */} else {*sdata = '\0';Invalid write if size==0 && bufsize==0
@ -0,0 +136,4 @@err = CRYPT_INVALID_PACKET;goto error;} else {if ((err = mp_read_unsigned_bin(vdata, (unsigned char *)in, size)) != CRYPT_OK) { goto error; }Check that the top bit isn't set, that implies a negative value
https://tools.ietf.org/html/rfc4251#page-8
@ -0,0 +136,4 @@err = CRYPT_INVALID_PACKET;goto error;} else {if ((err = mp_read_unsigned_bin(vdata, (unsigned char *)in, size)) != CRYPT_OK) { goto error; }Good catch. Done.
@ -0,0 +123,4 @@XSTRNCPY(sdata, (const char *)in, size);sdata[size] = '\0'; /* strncpy doesn't NUL-terminate */} else {*sdata = '\0';Moved the
size >= bufsizecheck up, out of theif (size > 0)case. That way if the string is 0 bytes long - an empty string - we still need a buffer of at least 1 byte to output a C-style NUL-terminated string.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 -
github.com/karel-m/dropbear@4530ff6897- on a PR adopting Dropbear to the new libtomcrypt ecc_* stuff I saw the dark magic in dropbear'secc.crelated to ECDSA signatures which lead me to the idea that libtomcrypt should do a better job supporting our "prominent customer" (= dropbear).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
dropbear_ecc_curve.oidin 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?
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
ecc_sign_hash_exandecc_verify_hash_exforces 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)
We could put that branch of the conditional on
#ifdef LTC_DERsoLTC_ECCSIG_ANSIX962is only available as a signature format if the library is built with DER support and returns an error otherwise.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.candecc_verify_hash_ex.cinto more.cfiles.Cheers! 👍