diff options
author | Matt Johnston <matt@ucc.asn.au> | 2004-08-08 16:17:05 +0000 |
---|---|---|
committer | Matt Johnston <matt@ucc.asn.au> | 2004-08-08 16:17:05 +0000 |
commit | 39dce0098015867dcea115c88ef485485cd6032a (patch) | |
tree | 3863ec9a3a9dcc14d9c0ec96f83bd64f288fa07f | |
parent | 333eac7f9a976d646e319a044923872a9b876789 (diff) |
- Hostkey checking is mostly there, just aren't appending yet.
- Rearranged various bits of the fingerprint/base64 type code, so it
can be shared between versions
--HG--
extra : convert_revision : 6b8ab4ec5a6c99733fff584231b81ad9636ff15e
-rw-r--r-- | cli-kex.c | 137 | ||||
-rw-r--r-- | dbutil.c | 46 | ||||
-rw-r--r-- | options.h | 4 | ||||
-rw-r--r-- | signkey.c | 84 | ||||
-rw-r--r-- | signkey.h | 5 | ||||
-rw-r--r-- | svr-authpubkey.c | 93 |
6 files changed, 258 insertions, 111 deletions
@@ -37,6 +37,8 @@ #include "signkey.h" +static void checkhostkey(unsigned char* keyblob, unsigned int keybloblen); +#define MAX_KNOWNHOSTS_LINE 4500 void send_msg_kexdh_init() { @@ -58,14 +60,22 @@ void recv_msg_kexdh_reply() { mp_int dh_f; sign_key *hostkey = NULL; - int type, keylen; + unsigned int type, keybloblen; + unsigned char* keyblob = NULL; + TRACE(("enter recv_msg_kexdh_reply")); type = ses.newkeys->algo_hostkey; TRACE(("type is %d", type)); hostkey = new_sign_key(); - keylen = buf_getint(ses.payload); + keybloblen = buf_getint(ses.payload); + + keyblob = buf_getptr(ses.payload, keybloblen); + if (!ses.kexstate.donefirstkex) { + /* Only makes sense the first time */ + checkhostkey(keyblob, keybloblen); + } if (buf_get_pub_key(ses.payload, hostkey, &type) != DROPBEAR_SUCCESS) { TRACE(("failed getting pubkey")); @@ -86,9 +96,6 @@ void recv_msg_kexdh_reply() { dropbear_exit("Bad hostkey signature"); } - /* XXX TODO */ - dropbear_log(LOG_WARNING,"Not checking hostkey fingerprint for the moment"); - sign_key_free(hostkey); hostkey = NULL; @@ -96,3 +103,123 @@ void recv_msg_kexdh_reply() { ses.requirenext = SSH_MSG_NEWKEYS; TRACE(("leave recv_msg_kexdh_init")); } + +static void ask_to_confirm(unsigned char* keyblob, unsigned int keybloblen) { + + char* fp = NULL; + + fp = sign_key_fingerprint(keyblob, keybloblen); + fprintf(stderr, "\nHost '%s' is not in the trusted hosts file.\n(fingerprint %s)\nDo you want to continue connecting? (y/n)\n", + cli_opts.remotehost, + fp); + + if (getc(stdin) == 'y') { + m_free(fp); + return; + } + + dropbear_exit("Didn't validate host key"); +} + +static void checkhostkey(unsigned char* keyblob, unsigned int keybloblen) { + + char * filename = NULL; + FILE *hostsfile = NULL; + struct passwd *pw = NULL; + unsigned int len, hostlen; + const char *algoname = NULL; + buffer * line = NULL; + int ret; + + pw = getpwuid(getuid()); + + if (pw == NULL) { + dropbear_exit("Failed to get homedir"); + } + + len = strlen(pw->pw_dir); + filename = m_malloc(len + 18); /* "/.ssh/known_hosts" and null-terminator*/ + + snprintf(filename, len+18, "%s/.ssh", pw->pw_dir); + /* Check that ~/.ssh exists - easiest way is just to mkdir */ + if (mkdir(filename, S_IRWXU) != 0) { + if (errno != EEXIST) { + ask_to_confirm(keyblob, keybloblen); + goto out; /* only get here on success */ + } + } + + snprintf(filename, len+18, "%s/.ssh/known_hosts", pw->pw_dir); + hostsfile = fopen(filename, "r+"); + if (hostsfile == NULL) { + ask_to_confirm(keyblob, keybloblen); + goto out; /* We only get here on success */ + } + + line = buf_new(MAX_KNOWNHOSTS_LINE); + hostlen = strlen(cli_opts.remotehost); + + do { + if (buf_getline(line, hostsfile) == DROPBEAR_FAILURE) { + TRACE(("failed reading line: prob EOF")); + break; + } + + /* The line is too short to be sensible */ + /* "30" is 'enough to hold ssh-dss plus the spaces, ie so we don't + * buf_getfoo() past the end and die horribly - the base64 parsing + * code is what tiptoes up to the end nicely */ + if (line->len < (hostlen+30) ) { + TRACE(("line is too short to be sensible")); + continue; + } + + /* Compare hostnames */ + if (strncmp(cli_opts.remotehost, buf_getptr(line, hostlen), + hostlen) != 0) { + TRACE(("hosts don't match")); + continue; + } + + buf_incrpos(line, hostlen); + if (buf_getbyte(line) != ' ') { + /* there wasn't a space after the hostname, something dodgy */ + TRACE(("missing space afte matching hostname")); + continue; + } + + algoname = signkey_name_from_type(ses.newkeys->algo_hostkey, &len); + if ( strncmp(buf_getptr(line, len), algoname, len) != 0) { + TRACE(("algo doesn't match")); + continue; + } + + buf_incrpos(line, len); + if (buf_getbyte(line) != ' ') { + TRACE(("missing space after algo")); + continue; + } + + /* Now we're at the interesting hostkey */ + ret = cmp_base64_key(keyblob, keybloblen, algoname, len, line); + + if (ret == DROPBEAR_SUCCESS) { + /* Good matching key */ + TRACE(("good matching key")); + goto out; + } + + /* The keys didn't match. eep. */ + } while (1); /* keep going 'til something happens */ + + /* Key doesn't exist yet */ + ask_to_confirm(keyblob, keybloblen); + /* If we get here, they said yes */ + +out: + if (hostsfile != NULL) { + fclose(hostsfile); + } + m_free(filename); + buf_free(line); +} @@ -320,6 +320,52 @@ int buf_readfile(buffer* buf, const char* filename) { return DROPBEAR_SUCCESS; } +/* get a line from the file into buffer in the style expected for an + * authkeys file. + * Will return DROPBEAR_SUCCESS if data is read, or DROPBEAR_FAILURE on EOF.*/ +/* Only used for ~/.ssh/known_hosts and ~/.ssh/authorized_keys */ +#if defined(DROPBEAR_CLIENT) || defined(DROPBEAR_PUBKEY_AUTH) +int buf_getline(buffer * line, FILE * authfile) { + + int c = EOF; + + TRACE(("enter buf_getline")); + + buf_setpos(line, 0); + buf_setlen(line, 0); + + while (line->pos < line->size) { + + c = fgetc(authfile); /*getc() is weird with some uClibc systems*/ + if (c == EOF || c == '\n' || c == '\r') { + goto out; + } + + buf_putbyte(line, (unsigned char)c); + } + + TRACE(("leave getauthline: line too long")); + /* We return success, but the line length will be zeroed - ie we just + * ignore that line */ + buf_setlen(line, 0); + +out: + + buf_setpos(line, 0); + + /* if we didn't read anything before EOF or error, exit */ + if (c == EOF && line->pos == 0) { + TRACE(("leave getauthline: failure")); + return DROPBEAR_FAILURE; + } else { + TRACE(("leave getauthline: success")); + return DROPBEAR_SUCCESS; + } + + TRACE(("leave buf_getline")); +} +#endif + /* loop until the socket is closed (in case of EINTR) or * we get and error. * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ @@ -301,6 +301,10 @@ #define USING_LISTENERS #endif +#if defined(DROPBEAR_CLIENT) || defined(DROPBEAR_PUBKEY_AUTH) +#define DROPBEAR_KEY_LINES /* ie we're using authorized_keys or known_hosts */ +#endif + /* We use dropbear_client and dropbear_server as shortcuts to avoid redundant * code, if we're just compiling as client or server */ #if defined(DROPBEAR_SERVER) && defined(DROPBEAR_CLIENT) @@ -272,25 +272,20 @@ static char hexdig(unsigned char x) { /* Since we're not sure if we'll have md5 or sha1, we present both. * MD5 is used in preference, but sha1 could still be useful */ #ifdef DROPBEAR_MD5_HMAC -static char * sign_key_md5_fingerprint(sign_key *key, int type) { +static char * sign_key_md5_fingerprint(unsigned char* keyblob, + unsigned int keybloblen) { char * ret; hash_state hs; - buffer *pubkeys; unsigned char hash[MD5_HASH_SIZE]; unsigned int h, i; unsigned int buflen; md5_init(&hs); - pubkeys = buf_new(1000); - buf_put_pub_key(pubkeys, key, type); /* skip the size int of the string - this is a bit messy */ - buf_setpos(pubkeys, 4); - md5_process(&hs, buf_getptr(pubkeys, pubkeys->len-pubkeys->pos), - pubkeys->len-pubkeys->pos); + md5_process(&hs, keyblob, keybloblen); - buf_free(pubkeys); md5_done(&hs, hash); /* "md5 hexfingerprinthere\0", each hex digit is "AB:" etc */ @@ -311,25 +306,20 @@ static char * sign_key_md5_fingerprint(sign_key *key, int type) { } #else /* use SHA1 rather than MD5 for fingerprint */ -static char * sign_key_sha1_fingerprint(sign_key *key, int type) { +static char * sign_key_sha1_fingerprint(unsigned char* keyblob, + unsigned int keybloblen) { char * ret; hash_state hs; - buffer *pubkeys; unsigned char hash[SHA1_HASH_SIZE]; unsigned int h, i; unsigned int buflen; sha1_init(&hs); - pubkeys = buf_new(1000); - buf_put_pub_key(pubkeys, key, type); - buf_setpos(pubkeys, 4); /* skip the size int of the string - this is a bit messy */ - sha1_process(&hs, buf_getptr(pubkeys, pubkeys->len-pubkeys->pos), - pubkeys->len-pubkeys->pos); + sha1_process(&hs, keyblob, keybloblen); - buf_free(pubkeys); sha1_done(&hs, hash); /* "sha1 hexfingerprinthere\0", each hex digit is "AB:" etc */ @@ -352,12 +342,12 @@ static char * sign_key_sha1_fingerprint(sign_key *key, int type) { /* This will return a freshly malloced string, containing a fingerprint * in either sha1 or md5 */ -char * sign_key_fingerprint(sign_key *key, int type) { +char * sign_key_fingerprint(unsigned char* keyblob, unsigned int keybloblen) { #ifdef DROPBEAR_MD5_HMAC - return sign_key_md5_fingerprint(key, type); + return sign_key_md5_fingerprint(keyblob, keybloblen); #else - return sign_key_sha1_fingerprint(key, type); + return sign_key_sha1_fingerprint(keyblob, keybloblen); #endif } @@ -427,3 +417,59 @@ int buf_verify(buffer * buf, sign_key *key, const unsigned char *data, return DROPBEAR_FAILURE; } #endif /* DROPBEAR_SIGNKEY_VERIFY */ + +#ifdef DROPBEAR_KEY_LINES /* ie we're using authorized_keys or known_hosts */ + +/* Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE when given a buffer containing + * a key, a key, and a type. The buffer is positioned at the start of the + * base64 data, and contains no trailing data */ +int cmp_base64_key(const unsigned char* keyblob, unsigned int keybloblen, + const unsigned char* algoname, unsigned int algolen, + buffer * line) { + + buffer * decodekey = NULL; + int ret = DROPBEAR_FAILURE; + unsigned int len, filealgolen; + unsigned long decodekeylen; + unsigned char* filealgo = NULL; + + /* now we have the actual data */ + len = line->len - line->pos; + decodekeylen = len * 2; /* big to be safe */ + decodekey = buf_new(decodekeylen); + + if (base64_decode(buf_getptr(line, len), len, + buf_getwriteptr(decodekey, decodekey->size), + &decodekeylen) != CRYPT_OK) { + TRACE(("checkpubkey: base64 decode failed")); + goto out; + } + TRACE(("checkpubkey: base64_decode success")); + buf_incrlen(decodekey, decodekeylen); + + /* compare the keys */ + if ( ( decodekeylen != keybloblen ) + || memcmp( buf_getptr(decodekey, decodekey->len), + keyblob, decodekey->len) != 0) { + TRACE(("checkpubkey: compare failed")); + goto out; + } + + /* ... and also check that the algo specified and the algo in the key + * itself match */ + filealgolen = buf_getint(decodekey); + filealgo = buf_getptr(decodekey, filealgolen); + if (filealgolen != algolen || memcmp(filealgo, algoname, algolen) != 0) { + TRACE(("checkpubkey: algo match failed")); + goto out; + } + + /* All checks passed */ + ret = DROPBEAR_SUCCESS; + +out: + buf_free(decodekey); + decodekey = NULL; + return ret; +} +#endif @@ -54,7 +54,10 @@ void buf_put_sign(buffer* buf, sign_key *key, int type, #ifdef DROPBEAR_SIGNKEY_VERIFY int buf_verify(buffer * buf, sign_key *key, const unsigned char *data, unsigned int len); -char * sign_key_fingerprint(sign_key *key, int type); +char * sign_key_fingerprint(unsigned char* keyblob, unsigned int keybloblen); #endif +int cmp_base64_key(const unsigned char* keyblob, unsigned int keybloblen, + const unsigned char* algoname, unsigned int algolen, + buffer * line); #endif /* _SIGNKEY_H_ */ diff --git a/svr-authpubkey.c b/svr-authpubkey.c index e6cc606..53d5c06 100644 --- a/svr-authpubkey.c +++ b/svr-authpubkey.c @@ -38,7 +38,7 @@ #ifdef DROPBEAR_PUBKEY_AUTH #define MIN_AUTHKEYS_LINE 10 /* "ssh-rsa AB" - short but doesn't matter */ -#define MAX_AUTHKEYS_LINE 1000 /* max length of a line in authkeys */ +#define MAX_AUTHKEYS_LINE 4200 /* max length of a line in authkeys */ static int checkpubkey(unsigned char* algo, unsigned int algolen, unsigned char* keyblob, unsigned int keybloblen); @@ -46,7 +46,6 @@ static int checkpubkeyperms(); static void send_msg_userauth_pk_ok(unsigned char* algo, unsigned int algolen, unsigned char* keyblob, unsigned int keybloblen); static int checkfileperm(char * filename); -static int getauthline(buffer * line, FILE * authfile); /* process a pubkey auth request, sending success or failure message as * appropriate */ @@ -102,7 +101,7 @@ void svr_auth_pubkey() { buf_setpos(signbuf, 0); /* ... and finally verify the signature */ - fp = sign_key_fingerprint(key, type); + fp = sign_key_fingerprint(keyblob, keybloblen); if (buf_verify(ses.payload, key, buf_getptr(signbuf, signbuf->len), signbuf->len) == DROPBEAR_SUCCESS) { dropbear_log(LOG_NOTICE, @@ -160,10 +159,6 @@ static int checkpubkey(unsigned char* algo, unsigned int algolen, char * filename = NULL; int ret = DROPBEAR_FAILURE; buffer * line = NULL; - buffer * decodekey = NULL; - unsigned long decodekeylen; - unsigned char* filealgo = NULL; - unsigned int filealgolen; unsigned int len, pos; TRACE(("enter checkpubkey")); @@ -202,14 +197,8 @@ static int checkpubkey(unsigned char* algo, unsigned int algolen, /* iterate through the lines */ do { - /* free reused vars */ - if (decodekey) { - buf_free(decodekey); - decodekey = NULL; - } - m_free(filealgo); - if (getauthline(line, authfile) == DROPBEAR_FAILURE) { + if (buf_getline(line, authfile) == DROPBEAR_FAILURE) { /* EOF reached */ TRACE(("checkpubkey: authorized_keys EOF reached")); break; @@ -243,38 +232,12 @@ static int checkpubkey(unsigned char* algo, unsigned int algolen, TRACE(("checkpubkey: line pos = %d len = %d", line->pos, line->len)); - /* now we have the actual data */ - decodekeylen = (line->len - line->pos) * 2; - decodekey = buf_new(decodekeylen); - if (base64_decode(buf_getptr(line, line->len - line->pos), - line->len - line->pos, - buf_getwriteptr(decodekey, decodekey->size), - &decodekeylen) != CRYPT_OK) { - TRACE(("checkpubkey: base64 decode failed")); - continue; - } - TRACE(("checkpubkey: base64_decode success")); - buf_incrlen(decodekey, decodekeylen); - - /* compare the keys */ - if (decodekeylen != keybloblen || memcmp( - buf_getptr(decodekey, decodekey->len), - keyblob, decodekey->len) != 0) { - TRACE(("checkpubkey: compare failed")); - continue; - } - - /* and also check that the algo specified and the algo in the key - * itself match */ - filealgo = buf_getstring(decodekey, &filealgolen); - if (filealgolen != algolen || memcmp(filealgo, algo, algolen) != 0) { - TRACE(("checkpubkey: algo match failed")); - continue; + ret = cmp_base64_key(keyblob, keybloblen, algo, algolen, line); + if (ret == DROPBEAR_SUCCESS) { + break; } - /* now we know this key is good */ - ret = DROPBEAR_SUCCESS; - break; + /* We continue to the next line otherwise */ } while (1); @@ -285,53 +248,11 @@ out: if (line) { buf_free(line); } - if (decodekey) { - buf_free(decodekey); - } m_free(filename); - m_free(filealgo); TRACE(("leave checkpubkey: ret=%d", ret)); return ret; } -/* get a line from the file into buffer in the style expected for an - * authkeys file. - * Will return DROPBEAR_SUCCESS if data is read, or DROPBEAR_FAILURE on EOF.*/ -static int getauthline(buffer * line, FILE * authfile) { - - int c = EOF; - - TRACE(("enter getauthline")); - - buf_setpos(line, 0); - buf_setlen(line, 0); - - while (line->pos < line->size) { - c = fgetc(authfile); /*getc() is weird with some uClibc systems*/ - if (c == EOF || c == '\n' || c == '\r') { - goto out; - } - buf_putbyte(line, (unsigned char)c); - } - - TRACE(("leave getauthline: line too long")); - return DROPBEAR_FAILURE; - -out: - - buf_setpos(line, 0); - - /* if we didn't read anything before EOF or error, exit */ - if (c == EOF && line->pos == 0) { - TRACE(("leave getauthline: failure")); - return DROPBEAR_FAILURE; - } else { - TRACE(("leave getauthline: success")); - return DROPBEAR_SUCCESS; - } - - TRACE(("leave getauthline")); -} /* Returns DROPBEAR_SUCCESS if file permissions for pubkeys are ok, * DROPBEAR_FAILURE otherwise. |