X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/900852abf7638e66c76e73ce5b2f423301157388..HEAD:/sshdss.c diff --git a/sshdss.c b/sshdss.c index 74894673..84fcdac7 100644 --- a/sshdss.c +++ b/sshdss.c @@ -1,3 +1,7 @@ +/* + * Digital Signature Standard implementation for PuTTY. + */ + #include #include #include @@ -5,18 +9,6 @@ #include "ssh.h" #include "misc.h" -#define GET_32BIT(cp) \ - (((unsigned long)(unsigned char)(cp)[0] << 24) | \ - ((unsigned long)(unsigned char)(cp)[1] << 16) | \ - ((unsigned long)(unsigned char)(cp)[2] << 8) | \ - ((unsigned long)(unsigned char)(cp)[3])) - -#define PUT_32BIT(cp, value) { \ - (cp)[0] = (unsigned char)((value) >> 24); \ - (cp)[1] = (unsigned char)((value) >> 16); \ - (cp)[2] = (unsigned char)((value) >> 8); \ - (cp)[3] = (unsigned char)(value); } - static void sha_mpint(SHA_State * s, Bignum b) { unsigned char lenbuf[4]; @@ -28,7 +20,7 @@ static void sha_mpint(SHA_State * s, Bignum b) lenbuf[0] = bignum_byte(b, len); SHA_Bytes(s, lenbuf, 1); } - memset(lenbuf, 0, sizeof(lenbuf)); + smemclr(lenbuf, sizeof(lenbuf)); } static void sha512_mpint(SHA512_State * s, Bignum b) @@ -42,7 +34,7 @@ static void sha512_mpint(SHA512_State * s, Bignum b) lenbuf[0] = bignum_byte(b, len); SHA512_Bytes(s, lenbuf, 1); } - memset(lenbuf, 0, sizeof(lenbuf)); + smemclr(lenbuf, sizeof(lenbuf)); } static void getstring(char **data, int *datalen, char **p, int *length) @@ -50,7 +42,9 @@ static void getstring(char **data, int *datalen, char **p, int *length) *p = NULL; if (*datalen < 4) return; - *length = GET_32BIT(*data); + *length = toint(GET_32BIT(*data)); + if (*length < 0) + return; *datalen -= 4; *data += 4; if (*datalen < *length) @@ -78,6 +72,9 @@ static Bignum get160(char **data, int *datalen) { Bignum b; + if (*datalen < 20) + return NULL; + b = bignum_from_bytes((unsigned char *)*data, 20); *data += 20; *datalen -= 20; @@ -85,15 +82,15 @@ static Bignum get160(char **data, int *datalen) return b; } +static void dss_freekey(void *key); /* forward reference */ + static void *dss_newkey(char *data, int len) { char *p; int slen; struct dss_key *dss; - dss = smalloc(sizeof(struct dss_key)); - if (!dss) - return NULL; + dss = snew(struct dss_key); getstring(&data, &len, &p, &slen); #ifdef DEBUG_DSS @@ -106,7 +103,7 @@ static void *dss_newkey(char *data, int len) } #endif - if (!p || memcmp(p, "ssh-dss", 7)) { + if (!p || slen != 7 || memcmp(p, "ssh-dss", 7)) { sfree(dss); return NULL; } @@ -114,6 +111,14 @@ static void *dss_newkey(char *data, int len) dss->q = getmp(&data, &len); dss->g = getmp(&data, &len); dss->y = getmp(&data, &len); + dss->x = NULL; + + if (!dss->p || !dss->q || !dss->g || !dss->y || + !bignum_cmp(dss->q, Zero) || !bignum_cmp(dss->p, Zero)) { + /* Invalid key. */ + dss_freekey(dss); + return NULL; + } return dss; } @@ -121,10 +126,16 @@ static void *dss_newkey(char *data, int len) static void dss_freekey(void *key) { struct dss_key *dss = (struct dss_key *) key; - freebn(dss->p); - freebn(dss->q); - freebn(dss->g); - freebn(dss->y); + if (dss->p) + freebn(dss->p); + if (dss->q) + freebn(dss->q); + if (dss->g) + freebn(dss->g); + if (dss->y) + freebn(dss->y); + if (dss->x) + freebn(dss->x); sfree(dss); } @@ -141,7 +152,7 @@ static char *dss_fmtkey(void *key) len += 4 * (bignum_bitcount(dss->q) + 15) / 16; len += 4 * (bignum_bitcount(dss->g) + 15) / 16; len += 4 * (bignum_bitcount(dss->y) + 15) / 16; - p = smalloc(len); + p = snewn(len, char); if (!p) return NULL; @@ -209,7 +220,7 @@ static char *dss_fingerprint(void *key) for (i = 0; i < 16; i++) sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "", digest[i]); - ret = smalloc(strlen(buffer) + 1); + ret = snewn(strlen(buffer) + 1, char); if (ret) strcpy(ret, buffer); return ret; @@ -239,14 +250,14 @@ static int dss_verifysig(void *key, char *sig, int siglen, #endif /* * Commercial SSH (2.0.13) and OpenSSH disagree over the format - * of a DSA signature. OpenSSH is in line with the IETF drafts: + * of a DSA signature. OpenSSH is in line with RFC 4253: * it uses a string "ssh-dss", followed by a 40-byte string * containing two 160-bit integers end-to-end. Commercial SSH * can't be bothered with the header bit, and considers a DSA * signature blob to be _just_ the 40-byte string containing * the two 160-bit integers. We tell them apart by measuring * the length: length 40 means the commercial-SSH bug, anything - * else is assumed to be IETF-compliant. + * else is assumed to be RFC-compliant. */ if (siglen != 40) { /* bug not present; read admin fields */ getstring(&sig, &siglen, &p, &slen); @@ -257,13 +268,29 @@ static int dss_verifysig(void *key, char *sig, int siglen, } r = get160(&sig, &siglen); s = get160(&sig, &siglen); - if (!r || !s) + if (!r || !s) { + if (r) + freebn(r); + if (s) + freebn(s); return 0; + } + + if (!bignum_cmp(s, Zero)) { + freebn(r); + freebn(s); + return 0; + } /* * Step 1. w <- s^-1 mod q. */ w = modinv(s, dss->q); + if (!w) { + freebn(r); + freebn(s); + return 0; + } /* * Step 2. u1 <- SHA(message) * w mod q. @@ -295,6 +322,8 @@ static int dss_verifysig(void *key, char *sig, int siglen, freebn(w); freebn(sha); + freebn(u1); + freebn(u2); freebn(gu1p); freebn(yu2p); freebn(gu1yu2p); @@ -322,7 +351,7 @@ static unsigned char *dss_public_blob(void *key, int *len) * 27 + sum of lengths. (five length fields, 20+7=27). */ bloblen = 27 + plen + qlen + glen + ylen; - blob = smalloc(bloblen); + blob = snewn(bloblen, unsigned char); p = blob; PUT_32BIT(p, 7); p += 4; @@ -362,7 +391,7 @@ static unsigned char *dss_private_blob(void *key, int *len) * mpint x, string[20] the SHA of p||q||g. Total 4 + xlen. */ bloblen = 4 + xlen; - blob = smalloc(bloblen); + blob = snewn(bloblen, unsigned char); p = blob; PUT_32BIT(p, xlen); p += 4; @@ -385,7 +414,13 @@ static void *dss_createkey(unsigned char *pub_blob, int pub_len, Bignum ytest; dss = dss_newkey((char *) pub_blob, pub_len); + if (!dss) + return NULL; dss->x = getmp(&pb, &priv_len); + if (!dss->x) { + dss_freekey(dss); + return NULL; + } /* * Check the obsolete hash in the old DSS key format. @@ -410,6 +445,7 @@ static void *dss_createkey(unsigned char *pub_blob, int pub_len, ytest = modpow(dss->g, dss->x, dss->p); if (0 != bignum_cmp(ytest, dss->y)) { dss_freekey(dss); + freebn(ytest); return NULL; } freebn(ytest); @@ -422,9 +458,7 @@ static void *dss_openssh_createkey(unsigned char **blob, int *len) char **b = (char **) blob; struct dss_key *dss; - dss = smalloc(sizeof(struct dss_key)); - if (!dss) - return NULL; + dss = snew(struct dss_key); dss->p = getmp(b, len); dss->q = getmp(b, len); @@ -432,14 +466,11 @@ static void *dss_openssh_createkey(unsigned char **blob, int *len) dss->y = getmp(b, len); dss->x = getmp(b, len); - if (!dss->p || !dss->q || !dss->g || !dss->y || !dss->x) { - sfree(dss->p); - sfree(dss->q); - sfree(dss->g); - sfree(dss->y); - sfree(dss->x); - sfree(dss); - return NULL; + if (!dss->p || !dss->q || !dss->g || !dss->y || !dss->x || + !bignum_cmp(dss->q, Zero) || !bignum_cmp(dss->p, Zero)) { + /* Invalid key. */ + dss_freekey(dss); + return NULL; } return dss; @@ -473,6 +504,20 @@ static int dss_openssh_fmtkey(void *key, unsigned char *blob, int len) return bloblen; } +static int dss_pubkey_bits(void *blob, int len) +{ + struct dss_key *dss; + int ret; + + dss = dss_newkey((char *) blob, len); + if (!dss) + return -1; + ret = bignum_bitcount(dss->p); + dss_freekey(dss); + + return ret; +} + static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen) { /* @@ -569,18 +614,34 @@ static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen) SHA512_Init(&ss); SHA512_Bytes(&ss, digest512, sizeof(digest512)); SHA512_Bytes(&ss, digest, sizeof(digest)); - SHA512_Final(&ss, digest512); - memset(&ss, 0, sizeof(ss)); + while (1) { + SHA512_State ss2 = ss; /* structure copy */ + SHA512_Final(&ss2, digest512); + + smemclr(&ss2, sizeof(ss2)); + + /* + * Now convert the result into a bignum, and reduce it mod q. + */ + proto_k = bignum_from_bytes(digest512, 64); + k = bigmod(proto_k, dss->q); + freebn(proto_k); + kinv = modinv(k, dss->q); /* k^-1 mod q */ + if (!kinv) { /* very unlikely */ + freebn(k); + /* Perturb the hash to think of a different k. */ + SHA512_Bytes(&ss, "x", 1); + /* Go round and try again. */ + continue; + } + + break; + } - /* - * Now convert the result into a bignum, and reduce it mod q. - */ - proto_k = bignum_from_bytes(digest512, 64); - k = bigmod(proto_k, dss->q); - freebn(proto_k); + smemclr(&ss, sizeof(ss)); - memset(digest512, 0, sizeof(digest512)); + smemclr(digest512, sizeof(digest512)); /* * Now we have k, so just go ahead and compute the signature. @@ -590,11 +651,11 @@ static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen) freebn(gkp); hash = bignum_from_bytes(digest, 20); - kinv = modinv(k, dss->q); /* k^-1 mod q */ hxr = bigmuladd(dss->x, r, hash); /* hash + x*r */ s = modmul(kinv, hxr, dss->q); /* s = k^-1 * (hash + x*r) mod q */ freebn(hxr); freebn(kinv); + freebn(k); freebn(hash); /* @@ -606,7 +667,7 @@ static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen) * i.e. 4+7 + 4+40 bytes. */ nbytes = 4 + 7 + 4 + 40; - bytes = smalloc(nbytes); + bytes = snewn(nbytes, unsigned char); PUT_32BIT(bytes, 7); memcpy(bytes + 4, "ssh-dss", 7); PUT_32BIT(bytes + 4 + 7, 40); @@ -630,6 +691,7 @@ const struct ssh_signkey ssh_dss = { dss_createkey, dss_openssh_createkey, dss_openssh_fmtkey, + dss_pubkey_bits, dss_fingerprint, dss_verifysig, dss_sign,