/* Retrieve an mp_int from the buffer. * Will fail for -ve since they shouldn't be required here. * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ int buf_getmpint(buffer* buf, mp_int* mp) { unsigned int len; len = buf_getint(buf); if (len == 0) { mp_zero(mp); return DROPBEAR_SUCCESS; } if (len > BUF_MAX_MPINT) { return DROPBEAR_FAILURE; } /* check for negative */ if (*buf_getptr(buf, 1) & (1 << (CHAR_BIT-1))) { return DROPBEAR_FAILURE; } if (mp_read_unsigned_bin(mp, buf_getptr(buf, len), len) != MP_OKAY) { return DROPBEAR_FAILURE; } buf_incrpos(buf, len); return DROPBEAR_SUCCESS; }
/* Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ static int buf_writefile(buffer * buf, const char * filename) { int ret = DROPBEAR_FAILURE; int fd = -1; fd = open(filename, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (fd < 0) { dropbear_log(LOG_ERR, "Couldn't create new file %s: %s", filename, strerror(errno)); goto out; } /* write the file now */ while (buf->pos != buf->len) { int len = write(fd, buf_getptr(buf, buf->len - buf->pos), buf->len - buf->pos); if (errno == EINTR) { continue; } if (len <= 0) { dropbear_log(LOG_ERR, "Failed writing file %s: %s", filename, strerror(errno)); goto out; } buf_incrpos(buf, len); } ret = DROPBEAR_SUCCESS; out: if (fd >= 0) { m_close(fd); } return ret; }
/* returns success or failure, and the keytype in *type. If we want * to restrict the type, type can contain a type to return */ int readhostkey(const char * filename, sign_key * hostkey, int *type) { int ret = DROPBEAR_FAILURE; buffer *buf; buf = buf_new(MAX_PRIVKEY_SIZE); if (buf_readfile(buf, filename) == DROPBEAR_FAILURE) { goto out; } buf_setpos(buf, 0); addrandom(buf_getptr(buf, buf->len), buf->len); if (buf_get_priv_key(buf, hostkey, type) == DROPBEAR_FAILURE) { goto out; } ret = DROPBEAR_SUCCESS; out: buf_burn(buf); buf_free(buf); return ret; }
/* Get an uint32 from the buffer and increment the pos */ unsigned int buf_getint(buffer* buf) { unsigned int ret; LOAD32H(ret, buf_getptr(buf, 4)); buf_incrpos(buf, 4); return ret; }
/* Checks the mac in hashbuf, for the data in readbuf. * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ static int checkmac(buffer* macbuf, buffer* sourcebuf) { unsigned char macsize; hmac_state hmac; unsigned char tempbuf[MAX_MAC_LEN]; unsigned long hashsize; int len; macsize = ses.keys->recv_algo_mac->hashsize; if (macsize == 0) { return DROPBEAR_SUCCESS; } /* calculate the mac */ if (hmac_init(&hmac, find_hash(ses.keys->recv_algo_mac->hashdesc->name), ses.keys->recvmackey, ses.keys->recv_algo_mac->keysize) != CRYPT_OK) { dropbear_exit("HMAC error"); } /* sequence number */ STORE32H(ses.recvseq, tempbuf); if (hmac_process(&hmac, tempbuf, 4) != CRYPT_OK) { dropbear_exit("HMAC error"); } buf_setpos(sourcebuf, 0); len = sourcebuf->len; if (hmac_process(&hmac, buf_getptr(sourcebuf, len), len) != CRYPT_OK) { dropbear_exit("HMAC error"); } hashsize = sizeof(tempbuf); if (hmac_done(&hmac, tempbuf, &hashsize) != CRYPT_OK) { dropbear_exit("HMAC error"); } /* compare the hash */ if (memcmp(tempbuf, buf_getptr(macbuf, macsize), macsize) != 0) { return DROPBEAR_FAILURE; } else { return DROPBEAR_SUCCESS; } }
static void printpubkey(sign_key * key, int keytype) { buffer * buf = NULL; unsigned char base64key[MAX_PUBKEY_SIZE*2]; unsigned long base64len; int err; const char * typestring = NULL; char *fp = NULL; int len; struct passwd * pw = NULL; char * username = NULL; char hostname[100]; buf = buf_new(MAX_PUBKEY_SIZE); buf_put_pub_key(buf, key, keytype); buf_setpos(buf, 4); len = buf->len - buf->pos; base64len = sizeof(base64key); err = base64_encode(buf_getptr(buf, len), len, base64key, &base64len); if (err != CRYPT_OK) { fprintf(stderr, "base64 failed"); } typestring = signkey_name_from_type(keytype, NULL); fp = sign_key_fingerprint(buf_getptr(buf, len), len); /* a user@host comment is informative */ username = ""; pw = getpwuid(getuid()); if (pw) { username = pw->pw_name; } gethostname(hostname, sizeof(hostname)); hostname[sizeof(hostname)-1] = '\0'; printf("Public key portion is:\n%s %s %s@%s\nFingerprint: %s\n", typestring, base64key, username, hostname, fp); m_free(fp); buf_free(buf); }
/* Handle a diffie-hellman key exchange reply. */ void recv_msg_kexdh_reply() { DEF_MP_INT(dh_f); sign_key *hostkey = NULL; unsigned int type, keybloblen; unsigned char* keyblob = NULL; TRACE(("enter recv_msg_kexdh_reply")) if (cli_ses.kex_state != KEXDH_INIT_SENT) { dropbear_exit("Received out-of-order kexdhreply"); } m_mp_init(&dh_f); type = ses.newkeys->algo_hostkey; TRACE(("type is %d", type)) hostkey = new_sign_key(); 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")) dropbear_exit("Bad KEX packet"); } if (buf_getmpint(ses.payload, &dh_f) != DROPBEAR_SUCCESS) { TRACE(("failed getting mpint")) dropbear_exit("Bad KEX packet"); } kexdh_comb_key(cli_ses.dh_e, cli_ses.dh_x, &dh_f, hostkey); mp_clear(&dh_f); mp_clear_multi(cli_ses.dh_e, cli_ses.dh_x, NULL); m_free(cli_ses.dh_e); m_free(cli_ses.dh_x); if (buf_verify(ses.payload, hostkey, ses.hash, SHA1_HASH_SIZE) != DROPBEAR_SUCCESS) { dropbear_exit("Bad hostkey signature"); } sign_key_free(hostkey); hostkey = NULL; send_msg_newkeys(); ses.requirenext = SSH_MSG_NEWKEYS; TRACE(("leave recv_msg_kexdh_init")) }
/* helper for svr_add_pubkey_options. returns DROPBEAR_SUCCESS if the option is matched, and increments the options_buf */ static int match_option(buffer *options_buf, const char *opt_name) { const unsigned int len = strlen(opt_name); if (options_buf->len - options_buf->pos < len) { return DROPBEAR_FAILURE; } if (strncasecmp(buf_getptr(options_buf, len), opt_name, len) == 0) { buf_incrpos(options_buf, len); return DROPBEAR_SUCCESS; } return DROPBEAR_FAILURE; }
/* Return a string as a newly allocated buffer */ buffer * buf_getstringbuf(buffer *buf) { buffer *ret = NULL; unsigned int len = buf_getint(buf); if (len > MAX_STRING_LEN) { dropbear_exit("String too long"); } ret = buf_new(len); memcpy(buf_getwriteptr(ret, len), buf_getptr(buf, len), len); buf_incrpos(buf, len); buf_incrlen(ret, len); return ret; }
/* Executed upon receiving a kexinit message from the client to initiate * key exchange. If we haven't already done so, we send the list of our * preferred algorithms. The client's requested algorithms are processed, * and we calculate the first portion of the key-exchange-hash for used * later in the key exchange. No response is sent, as the client should * initiate the diffie-hellman key exchange */ void recv_msg_kexinit() { TRACE(("enter recv_msg_kexinit")); if (!ses.kexstate.sentkexinit) { /* we need to send a kex packet */ send_msg_kexinit(); TRACE(("continue recv_msg_kexinit: sent kexinit")); } /* read the client's choice of algos */ read_kex(); /* start the kex hash */ ses.kexhashbuf = buf_new(MAX_KEXHASHBUF); /* V_C, the client's version string (CR and NL excluded) */ buf_putstring(ses.kexhashbuf, ses.remoteident, strlen((char*)ses.remoteident)); /* V_S, the server's version string (CR and NL excluded) */ buf_putstring(ses.kexhashbuf, (unsigned char*)LOCAL_IDENT, strlen(LOCAL_IDENT)); /* I_C, the payload of the client's SSH_MSG_KEXINIT */ buf_setpos(ses.payload, 0); buf_putstring(ses.kexhashbuf, buf_getptr(ses.payload, ses.payload->len), ses.payload->len); /* I_S, the payload of the server's SSH_MSG_KEXINIT */ buf_putstring(ses.kexhashbuf, buf_getptr(ses.transkexinit, ses.transkexinit->len), ses.transkexinit->len); buf_free(ses.transkexinit); ses.transkexinit = NULL; /* the rest of ses.kexhashbuf will be done after DH exchange */ ses.kexstate.recvkexinit = 1; ses.expecting = SSH_MSG_KEXDH_INIT; TRACE(("leave recv_msg_kexinit")); }
/* Non-blocking function reading available portion of a packet into the * ses's buffer, decrypting the length if encrypted, decrypting the * full portion if possible */ void read_packet() { int len; unsigned int maxlen; unsigned char blocksize; TRACE(("enter read_packet")); blocksize = ses.keys->recv_algo_crypt->blocksize; if (ses.readbuf == NULL || ses.readbuf->len < blocksize) { /* In the first blocksize of a packet */ /* Read the first blocksize of the packet, so we can decrypt it and * find the length of the whole packet */ read_packet_init(); /* If we don't have the length of decryptreadbuf, we didn't read * a whole blocksize and should exit */ if (ses.decryptreadbuf->len == 0) { TRACE(("leave read_packet: packetinit done")); return; } } /* Attempt to read the remainder of the packet, note that there * mightn't be any available (EAGAIN) */ assert(ses.readbuf != NULL); maxlen = ses.readbuf->len - ses.readbuf->pos; len = read(ses.sock, buf_getptr(ses.readbuf, maxlen), maxlen); buf_incrpos(ses.readbuf, len); if (len == 0) { dropbear_close("remote host closed connection"); } if (len < 0) { if (errno == EINTR || errno == EAGAIN) { TRACE(("leave read_packet: EINTR or EAGAIN")); return; } else { dropbear_exit("error reading"); } } if ((unsigned int)len == maxlen) { /* The whole packet has been read */ decrypt_packet(); /* The main select() loop in session.h will process_packet() to * handle the packet contents... */ } TRACE(("leave read_packet")); }
/* hash the ssh representation of the mp_int mp */ void sha1_process_mp(hash_state *hs, mp_int *mp) { int i; buffer * buf; buf = buf_new(512 + 20); /* max buffer is a 4096 bit key, plus header + some leeway*/ buf_putmpint(buf, mp); i = buf->pos; buf_setpos(buf, 0); sha1_process(hs, buf_getptr(buf, i), i); buf_free(buf); }
char * algolist_string(algo_type algos[]) { char *ret_list; buffer *b = buf_new(200); buf_put_algolist(b, algos); buf_setpos(b, b->len); buf_putbyte(b, '\0'); buf_setpos(b, 4); ret_list = m_strdup((const char *) buf_getptr(b, b->len - b->pos)); buf_free(b); return ret_list; }
/* when we receive channel data, put it in a buffer for writing to the program/ * shell etc */ void recv_msg_channel_data() { unsigned int chan; struct Channel * channel; unsigned int datalen; unsigned int pos; unsigned int maxdata; TRACE(("enter recv_msg_channel_data")); chan = buf_getint(ses.payload); channel = getchannel(chan); if (channel == NULL) { /* disconnect ? */ dropbear_exit("Unknown channel"); } assert(channel->infd != -1); datalen = buf_getint(ses.payload); /* if the client is going to send us more data than we've allocated, then * it has ignored the windowsize, so we "MAY ignore all extra data" */ maxdata = channel->writebuf->size - channel->writebuf->pos; if (datalen > maxdata) { TRACE(("Warning: recv_msg_channel_data: extra data past window")); datalen = maxdata; } /* write to the buffer - we always append to the end of the buffer */ pos = channel->writebuf->pos; buf_setpos(channel->writebuf, channel->writebuf->len); memcpy(buf_getwriteptr(channel->writebuf, datalen), buf_getptr(ses.payload, datalen), datalen); buf_incrwritepos(channel->writebuf, datalen); buf_setpos(channel->writebuf, pos); /* revert pos */ channel->recvwindow -= datalen; /* if (channel->recvwindow < RECV_MINWINDOW) { send_msg_channel_window_adjust(channel, RECV_MAXWINDOW - channel->recvwindow); channel->recvwindow = RECV_MAXWINDOW; }*/ TRACE(("leave recv_msg_channel_data")); }
/* Called to write data out to the server side of a channel (eg a shell or a * program. * Only called when we know we can write to a channel, writes as much as * possible */ static void writechannel(struct Channel* channel) { int len, maxlen; buffer *buf; TRACE(("enter writechannel")); if (channel->recveof || channel->sentclosed) { TRACE(("leave writechannel: already recveof or sentclosed")); return; } buf = channel->writebuf; maxlen = buf->len - buf->pos; len = write(channel->infd, buf_getptr(buf, maxlen), maxlen); if (len <= 0) { if (errno != EINTR) { /* no more to write */ channel->recveof = 1; /* if everything's closed then close it all up */ if (channel->transeof && (channel->erreof || channel->errfd == -1)) { send_msg_channel_close(channel); } } TRACE(("leave writechannel: len <= 0")); return; } /* extend the window */ /* TODO - this is inefficient */ if (len == maxlen) { buf_setpos(buf, 0); buf_setlen(buf, 0); send_msg_channel_window_adjust(channel, buf->size - channel->recvwindow); channel->recvwindow = buf->size; } else { buf_incrpos(buf, len); } TRACE(("leave writechannel")); }
/* Create the packet mac, and append H(seqno|clearbuf) to the output */ static void writemac(buffer * outputbuffer, buffer * clearwritebuf) { int macsize; unsigned char seqbuf[4]; unsigned long hashsize; hmac_state hmac; TRACE(("enter writemac")); macsize = ses.keys->trans_algo_mac->hashsize; if (macsize > 0) { /* calculate the mac */ if (hmac_init(&hmac, find_hash(ses.keys->trans_algo_mac->hashdesc->name), ses.keys->transmackey, ses.keys->trans_algo_mac->keysize) != CRYPT_OK) { dropbear_exit("HMAC error"); } /* sequence number */ STORE32H(ses.transseq, seqbuf); if (hmac_process(&hmac, seqbuf, 4) != CRYPT_OK) { dropbear_exit("HMAC error"); } /* the actual contents */ buf_setpos(clearwritebuf, 0); if (hmac_process(&hmac, buf_getptr(clearwritebuf, clearwritebuf->len), clearwritebuf->len) != CRYPT_OK) { dropbear_exit("HMAC error"); } hashsize = macsize; if (hmac_done(&hmac, buf_getwriteptr(outputbuffer, macsize), &hashsize) != CRYPT_OK) { dropbear_exit("HMAC error"); } buf_incrwritepos(outputbuffer, macsize); } TRACE(("leave writemac")); }
/* Return a null-terminated string, it is malloced, so must be free()ed * Note that the string isn't checked for null bytes, hence the retlen * may be longer than what is returned by strlen */ char* buf_getstring(buffer* buf, unsigned int *retlen) { unsigned int len; char* ret; len = buf_getint(buf); if (len > MAX_STRING_LEN) { dropbear_exit("String too long"); } if (retlen != NULL) { *retlen = len; } ret = m_malloc(len+1); memcpy(ret, buf_getptr(buf, len), len); buf_incrpos(buf, len); ret[len] = '\0'; return ret; }
/* compresses len bytes from src, outputting to dest (starting from the * respective current positions. */ static void buf_compress(buffer * dest, buffer * src, unsigned int len) { unsigned int endpos = src->pos + len; int result; TRACE(("enter buf_compress")); while (1) { ses.keys->trans_zstream->avail_in = endpos - src->pos; ses.keys->trans_zstream->next_in = buf_getptr(src, ses.keys->trans_zstream->avail_in); ses.keys->trans_zstream->avail_out = dest->size - dest->pos; ses.keys->trans_zstream->next_out = buf_getwriteptr(dest, ses.keys->trans_zstream->avail_out); result = deflate(ses.keys->trans_zstream, Z_SYNC_FLUSH); buf_setpos(src, endpos - ses.keys->trans_zstream->avail_in); buf_setlen(dest, dest->size - ses.keys->trans_zstream->avail_out); buf_setpos(dest, dest->len); if (result != Z_OK) { dropbear_exit("zlib error"); } if (ses.keys->trans_zstream->avail_in == 0) { break; } assert(ses.keys->trans_zstream->avail_out == 0); /* the buffer has been filled, we must extend. This only happens in * unusual circumstances where the data grows in size after deflate(), * but it is possible */ buf_resize(dest, dest->size + ZLIB_COMPRESS_INCR); } TRACE(("leave buf_compress")); }
/* non-blocking function writing out a current encrypted packet */ void write_packet() { int len, written; buffer * writebuf; TRACE(("enter write_packet")); assert(!isempty(&ses.writequeue)); /* Get the next buffer in the queue of encrypted packets to write*/ writebuf = (buffer*)examine(&ses.writequeue); len = writebuf->len - writebuf->pos; assert(len > 0); /* Try to write as much as possible */ written = write(ses.sock, buf_getptr(writebuf, len), len); if (written < 0) { if (errno == EINTR) { TRACE(("leave writepacket: EINTR")); return; } else { dropbear_exit("error writing"); } } if (written == 0) { dropbear_close("remote host closed connection"); } if (written == len) { /* We've finished with the packet, free it */ dequeue(&ses.writequeue); buf_free(writebuf); } else { /* More packet left to write, leave it in the queue for later */ buf_incrpos(writebuf, written); } TRACE(("leave write_packet")); }
/* returns a pointer to a newly created buffer */ static buffer* buf_decompress(buffer* buf, unsigned int len) { int result; buffer * ret; z_streamp zstream; zstream = ses.keys->recv_zstream; ret = buf_new(len); zstream->avail_in = len; zstream->next_in = buf_getptr(buf, len); /* decompress the payload, incrementally resizing the output buffer */ while (1) { zstream->avail_out = ret->size - ret->pos; zstream->next_out = buf_getwriteptr(ret, zstream->avail_out); result = inflate(zstream, Z_SYNC_FLUSH); buf_setlen(ret, ret->size - zstream->avail_out); buf_setpos(ret, ret->len); if (result != Z_BUF_ERROR && result != Z_OK) { dropbear_exit("zlib error"); } if (zstream->avail_in == 0 && (zstream->avail_out != 0 || result == Z_BUF_ERROR)) { /* we can only exit if avail_out hasn't all been used, * and there's no remaining input */ return ret; } if (zstream->avail_out == 0) { buf_resize(ret, ret->size + ZLIB_DECOMPRESS_INCR); } } }
/* non-blocking function writing out a current encrypted packet */ void write_packet() { int len, written; buffer * writebuf = NULL; time_t now; unsigned packet_type; int all_ignore = 1; #ifdef HAVE_WRITEV struct iovec *iov = NULL; int i; struct Link *l; #endif TRACE2(("enter write_packet")) dropbear_assert(!isempty(&ses.writequeue)); #ifdef HAVE_WRITEV iov = m_malloc(sizeof(*iov) * ses.writequeue.count); for (l = ses.writequeue.head, i = 0; l; l = l->link, i++) { writebuf = (buffer*)l->item; packet_type = writebuf->data[writebuf->len-1]; len = writebuf->len - 1 - writebuf->pos; dropbear_assert(len > 0); all_ignore &= (packet_type == SSH_MSG_IGNORE); TRACE2(("write_packet writev #%d type %d len %d/%d", i, packet_type, len, writebuf->len-1)) iov[i].iov_base = buf_getptr(writebuf, len); iov[i].iov_len = len; } written = writev(ses.sock_out, iov, ses.writequeue.count); if (written < 0) { if (errno == EINTR) { m_free(iov); TRACE2(("leave writepacket: EINTR")) return; } else {
/* Process a password auth request, sending success or failure messages as * appropriate */ void passwordauth() { #ifdef HAVE_SHADOW_H struct spwd *spasswd; #endif char * passwdcrypt; /* the crypt from /etc/passwd or /etc/shadow */ char * testcrypt; /* crypt generated from the user's password sent */ unsigned char * password; unsigned int passwordlen; unsigned char changepw; passwdcrypt = ses.authstate.pw->pw_passwd; #ifdef HAVE_SHADOW_H /* get the shadow password if possible */ spasswd = getspnam(ses.authstate.pw->pw_name); if (spasswd != NULL && spasswd->sp_pwdp != NULL) { passwdcrypt = spasswd->sp_pwdp; } #endif #ifdef DEBUG_HACKCRYPT /* debugging crypt for non-root testing with shadows */ passwdcrypt = DEBUG_HACKCRYPT; #endif /* check for empty password - need to do this again here * since the shadow password may differ to that tested * in auth.c */ if (passwdcrypt[0] == '\0') { dropbear_log(LOG_WARNING, "disallowed login with empty password for '%s' from %s", ses.authstate.printableuser, ses.addrstring); send_msg_userauth_failure(0, 1); return; } /* check if client wants to change password */ changepw = buf_getbyte(ses.payload); if (changepw) { /* not implemented by this server */ send_msg_userauth_failure(0, 1); return; } password = buf_getstring(ses.payload, &passwordlen); /* clear the buffer containing the password */ buf_incrpos(ses.payload, -passwordlen - 4); m_burn(buf_getptr(ses.payload, passwordlen + 4), passwordlen + 4); /* the first bytes of passwdcrypt are the salt */ testcrypt = crypt((char*)password, passwdcrypt); if (strcmp(testcrypt, passwdcrypt) == 0) { /* successful authentication */ dropbear_log(LOG_NOTICE, "password auth succeeded for '%s' from %s", ses.authstate.printableuser, ses.addrstring); send_msg_userauth_success(); } else { dropbear_log(LOG_WARNING, "bad password attempt for '%s' from %s", ses.authstate.printableuser, ses.addrstring); send_msg_userauth_failure(0, 1); } m_burn(password, passwordlen); m_free(password); }
/* encrypt the writepayload, putting into writebuf, ready for write_packet() * to put on the wire */ void encrypt_packet() { unsigned char padlen; unsigned char blocksize, macsize; buffer * writebuf; /* the packet which will go on the wire */ buffer * clearwritebuf; /* unencrypted, possibly compressed */ TRACE(("enter encrypt_packet()")); TRACE(("encrypt_packet type is %d", ses.writepayload->data[0])); blocksize = ses.keys->trans_algo_crypt->blocksize; macsize = ses.keys->trans_algo_mac->hashsize; /* Encrypted packet len is payload+5, then worst case is if we are 3 away * from a blocksize multiple. In which case we need to pad to the * multiple, then add another blocksize (or MIN_PACKET_LEN) */ clearwritebuf = buf_new((ses.writepayload->len+4+1) + MIN_PACKET_LEN + 3 #ifndef DISABLE_ZLIB + ZLIB_COMPRESS_INCR /* bit of a kludge, but we can't know len*/ #endif ); buf_setlen(clearwritebuf, PACKET_PAYLOAD_OFF); buf_setpos(clearwritebuf, PACKET_PAYLOAD_OFF); buf_setpos(ses.writepayload, 0); #ifndef DISABLE_ZLIB /* compression */ if (ses.keys->trans_algo_comp == DROPBEAR_COMP_ZLIB) { buf_compress(clearwritebuf, ses.writepayload, ses.writepayload->len); } else #endif { memcpy(buf_getwriteptr(clearwritebuf, ses.writepayload->len), buf_getptr(ses.writepayload, ses.writepayload->len), ses.writepayload->len); buf_incrwritepos(clearwritebuf, ses.writepayload->len); } /* finished with payload */ buf_setpos(ses.writepayload, 0); buf_setlen(ses.writepayload, 0); /* length of padding - packet length must be a multiple of blocksize, * with a minimum of 4 bytes of padding */ padlen = blocksize - (clearwritebuf->len) % blocksize; if (padlen < 4) { padlen += blocksize; } /* check for min packet length */ if (clearwritebuf->len + padlen < MIN_PACKET_LEN) { padlen += blocksize; } buf_setpos(clearwritebuf, 0); /* packet length excluding the packetlength uint32 */ buf_putint(clearwritebuf, clearwritebuf->len + padlen - 4); /* padding len */ buf_putbyte(clearwritebuf, padlen); /* actual padding */ buf_setpos(clearwritebuf, clearwritebuf->len); buf_incrlen(clearwritebuf, padlen); genrandom(buf_getptr(clearwritebuf, padlen), padlen); /* do the actual encryption */ buf_setpos(clearwritebuf, 0); /* create a new writebuffer, this is freed when it has been put on the * wire by writepacket() */ writebuf = buf_new(clearwritebuf->len + macsize); if (ses.keys->trans_algo_crypt->cipherdesc == NULL) { /* copy it */ memcpy(buf_getwriteptr(writebuf, clearwritebuf->len), buf_getptr(clearwritebuf, clearwritebuf->len), clearwritebuf->len); buf_incrwritepos(writebuf, clearwritebuf->len); } else { /* encrypt it */ while (clearwritebuf->pos < clearwritebuf->len) { if (cbc_encrypt(buf_getptr(clearwritebuf, blocksize), buf_getwriteptr(writebuf, blocksize), &ses.keys->trans_symmetric_struct) != CRYPT_OK) { dropbear_exit("error encrypting"); } buf_incrpos(clearwritebuf, blocksize); buf_incrwritepos(writebuf, blocksize); } } /* now add a hmac and we're done */ writemac(writebuf, clearwritebuf); /* clearwritebuf is finished with */ buf_free(clearwritebuf); /* enqueue the packet for sending */ buf_setpos(writebuf, 0); enqueue(&ses.writequeue, (void*)writebuf); /* Update counts */ ses.kexstate.datatrans += writebuf->len; ses.transseq++; TRACE(("leave encrypt_packet()")); }
/* handle the received packet */ void decrypt_packet() { unsigned char blocksize; unsigned char macsize; unsigned int padlen; unsigned int len; TRACE(("enter decrypt_packet")); blocksize = ses.keys->recv_algo_crypt->blocksize; macsize = ses.keys->recv_algo_mac->hashsize; ses.kexstate.datarecv += ses.readbuf->len; /* we've already decrypted the first blocksize in read_packet_init */ buf_setpos(ses.readbuf, blocksize); buf_resize(ses.decryptreadbuf, ses.readbuf->len - macsize); buf_setlen(ses.decryptreadbuf, ses.decryptreadbuf->size); buf_setpos(ses.decryptreadbuf, blocksize); /* decrypt if encryption is set, memcpy otherwise */ if (ses.keys->recv_algo_crypt->cipherdesc == NULL) { /* copy it */ len = ses.readbuf->len - macsize - blocksize; memcpy(buf_getwriteptr(ses.decryptreadbuf, len), buf_getptr(ses.readbuf, len), len); } else { /* decrypt */ while (ses.readbuf->pos < ses.readbuf->len - macsize) { if (cbc_decrypt(buf_getptr(ses.readbuf, blocksize), buf_getwriteptr(ses.decryptreadbuf, blocksize), &ses.keys->recv_symmetric_struct) != CRYPT_OK) { dropbear_exit("error decrypting"); } buf_incrpos(ses.readbuf, blocksize); buf_incrwritepos(ses.decryptreadbuf, blocksize); } } /* check the hmac */ buf_setpos(ses.readbuf, ses.readbuf->len - macsize); if (checkmac(ses.readbuf, ses.decryptreadbuf) != DROPBEAR_SUCCESS) { dropbear_exit("Integrity error"); } /* readbuf no longer required */ buf_free(ses.readbuf); ses.readbuf = NULL; /* get padding length */ buf_setpos(ses.decryptreadbuf, PACKET_PADDING_OFF); padlen = buf_getbyte(ses.decryptreadbuf); /* payload length */ /* - 4 - 1 is for LEN and PADLEN values */ len = ses.decryptreadbuf->len - padlen - 4 - 1; if ((len > MAX_PAYLOAD_LEN) || (len < 1)) { dropbear_exit("bad packet size"); } buf_setpos(ses.decryptreadbuf, PACKET_PAYLOAD_OFF); #ifndef DISABLE_ZLIB if (ses.keys->recv_algo_comp == DROPBEAR_COMP_ZLIB) { /* decompress */ ses.payload = buf_decompress(ses.decryptreadbuf, len); } else #endif { /* copy payload */ ses.payload = buf_new(len); memcpy(ses.payload->data, buf_getptr(ses.decryptreadbuf, len), len); buf_incrlen(ses.payload, len); } buf_free(ses.decryptreadbuf); ses.decryptreadbuf = NULL; buf_setpos(ses.payload, 0); ses.recvseq++; TRACE(("leave decrypt_packet")); }
/* Function used to read the initial portion of a packet, and determine the * length. Only called during the first BLOCKSIZE of a packet. */ static void read_packet_init() { unsigned int maxlen; int len; unsigned char blocksize; unsigned char macsize; blocksize = ses.keys->recv_algo_crypt->blocksize; macsize = ses.keys->recv_algo_mac->hashsize; if (ses.readbuf == NULL) { /* start of a new packet */ ses.readbuf = buf_new(INIT_READBUF); assert(ses.decryptreadbuf == NULL); ses.decryptreadbuf = buf_new(blocksize); } maxlen = blocksize - ses.readbuf->pos; /* read the rest of the packet if possible */ len = read(ses.sock, buf_getwriteptr(ses.readbuf, maxlen), maxlen); if (len == 0) { dropbear_close("remote host closed connection"); } if (len < 0) { if (errno == EINTR) { TRACE(("leave read_packet_init: EINTR")); return; } dropbear_exit("error reading"); } buf_incrwritepos(ses.readbuf, len); if ((unsigned int)len != maxlen) { /* don't have enough bytes to determine length, get next time */ return; } /* now we have the first block, need to get packet length, so we decrypt * the first block (only need first 4 bytes) */ buf_setpos(ses.readbuf, 0); if (ses.keys->recv_algo_crypt->cipherdesc == NULL) { /* copy it */ memcpy(buf_getwriteptr(ses.decryptreadbuf, blocksize), buf_getptr(ses.readbuf, blocksize), blocksize); } else { /* decrypt it */ if (cbc_decrypt(buf_getptr(ses.readbuf, blocksize), buf_getwriteptr(ses.decryptreadbuf,blocksize), &ses.keys->recv_symmetric_struct) != CRYPT_OK) { dropbear_exit("error decrypting"); } } buf_setlen(ses.decryptreadbuf, blocksize); len = buf_getint(ses.decryptreadbuf) + 4 + macsize; buf_setpos(ses.readbuf, blocksize); /* check packet length */ if ((len > MAX_PACKET_LEN) || (len < MIN_PACKET_LEN + macsize) || ((len - macsize) % blocksize != 0)) { dropbear_exit("bad packet size"); } buf_resize(ses.readbuf, len); buf_setlen(ses.readbuf, len); }
/* Reads data from the server's program/shell/etc, and puts it in a * channel_data packet to send. * chan is the remote channel, isextended is 0 if it is normal data, 1 * if it is extended data. if it is extended, then the type is in * exttype */ static void send_msg_channel_data(struct Channel *channel, int isextended, unsigned int exttype) { buffer *buf; int len; unsigned int maxlen; int fd; TRACE(("enter send_msg_channel_data")); TRACE(("extended = %d type = %d", isextended, exttype)); CHECKCLEARTOWRITE(); assert(!channel->sentclosed); if (isextended) { if (channel->erreof) { TRACE(("leave send_msg_channel_data: erreof already set")); return; } assert(exttype == SSH_EXTENDED_DATA_STDERR); fd = channel->errfd; } else { if (channel->transeof) { TRACE(("leave send_msg_channel_data: transeof already set")); return; } fd = channel->outfd; } assert(fd >= 0); maxlen = MIN(channel->transwindow, channel->transmaxpacket); /* -(1+4+4) is SSH_MSG_CHANNEL_DATA, channel number, string length, and * exttype if is extended */ maxlen = MIN(maxlen, ses.writepayload->size - 1 - 4 - 4 - (isextended ? 4 : 0)); if (maxlen == 0) { TRACE(("leave send_msg_channel_data: no window")); return; /* the data will get written later */ } /* read the data */ buf = buf_new(maxlen); len = read(fd, buf_getwriteptr(buf, maxlen), maxlen); if (len <= 0) { /* on error etc, send eof */ if (errno != EINTR) { if (isextended) { channel->erreof = 1; } else { channel->transeof = 1; } if ((channel->erreof || channel->errfd == -1) && channel->transeof) { send_msg_channel_eof(channel); } } buf_free(buf); TRACE(("leave send_msg_channel_data: len <= 0, erreof %d transeof %d", channel->erreof, channel->transeof)); return; } buf_incrlen(buf, len); buf_putbyte(ses.writepayload, isextended ? SSH_MSG_CHANNEL_EXTENDED_DATA : SSH_MSG_CHANNEL_DATA); buf_putint(ses.writepayload, channel->remotechan); if (isextended) { buf_putint(ses.writepayload, exttype); } buf_putstring(ses.writepayload, buf_getptr(buf, len), len); buf_free(buf); channel->transwindow -= len; encrypt_packet(); TRACE(("leave send_msg_channel_data")); }
/* Generate our side of the diffie-hellman key exchange value (dh_f), and * calculate the session key using the diffie-hellman algorithm. Following * that, the session hash is calculated, and signed with RSA or DSS. The * result is sent to the client. * * See the ietf-secsh-transport draft, section 6, for details */ static void send_msg_kexdh_reply(mp_int *dh_e) { mp_int dh_p, dh_q, dh_g, dh_y, dh_f; unsigned char randbuf[DH_P_LEN]; int dh_q_len; hash_state hs; TRACE(("enter send_msg_kexdh_reply")); assert(ses.kexstate.recvkexinit); m_mp_init_multi(&dh_g, &dh_p, &dh_q, &dh_y, &dh_f, NULL); /* read the prime and generator*/ if (mp_read_unsigned_bin(&dh_p, (unsigned char*)dh_p_val, DH_P_LEN) != MP_OKAY) { dropbear_exit("Diffie-Hellman error"); } if (mp_set_int(&dh_g, dh_g_val) != MP_OKAY) { dropbear_exit("Diffie-Hellman error"); } /* calculate q = (p-1)/2 */ if (mp_sub_d(&dh_p, 1, &dh_y) != MP_OKAY) { /*dh_y is just a temp var here*/ dropbear_exit("Diffie-Hellman error"); } if (mp_div_2(&dh_y, &dh_q) != MP_OKAY) { dropbear_exit("Diffie-Hellman error"); } dh_q_len = mp_unsigned_bin_size(&dh_q); /* calculate our random value dh_y */ do { assert((unsigned int)dh_q_len <= sizeof(randbuf)); genrandom(randbuf, dh_q_len); if (mp_read_unsigned_bin(&dh_y, randbuf, dh_q_len) != MP_OKAY) { dropbear_exit("Diffie-Hellman error"); } } while (mp_cmp(&dh_y, &dh_q) == MP_GT || mp_cmp_d(&dh_y, 0) != MP_GT); /* f = g^y mod p */ if (mp_exptmod(&dh_g, &dh_y, &dh_p, &dh_f) != MP_OKAY) { dropbear_exit("Diffie-Hellman error"); } mp_clear(&dh_g); /* K = e^y mod p */ ses.dh_K = (mp_int*)m_malloc(sizeof(mp_int)); m_mp_init(ses.dh_K); if (mp_exptmod(dh_e, &dh_y, &dh_p, ses.dh_K) != MP_OKAY) { dropbear_exit("Diffie-Hellman error"); } /* clear no longer needed vars */ mp_clear_multi(&dh_y, &dh_p, &dh_q, NULL); /* Create the remainder of the hash buffer, to generate the exchange hash */ /* K_S, the host key */ buf_put_pub_key(ses.kexhashbuf, ses.opts->hostkey, ses.newkeys->algo_hostkey); /* e, exchange value sent by the client */ buf_putmpint(ses.kexhashbuf, dh_e); /* f, exchange value sent by the server */ buf_putmpint(ses.kexhashbuf, &dh_f); /* K, the shared secret */ buf_putmpint(ses.kexhashbuf, ses.dh_K); /* calculate the hash H to sign */ sha1_init(&hs); buf_setpos(ses.kexhashbuf, 0); sha1_process(&hs, buf_getptr(ses.kexhashbuf, ses.kexhashbuf->len), ses.kexhashbuf->len); sha1_done(&hs, ses.hash); buf_free(ses.kexhashbuf); ses.kexhashbuf = NULL; /* first time around, we set the session_id to H */ if (ses.session_id == NULL) { /* create the session_id, this never needs freeing */ ses.session_id = (unsigned char*)m_malloc(SHA1_HASH_SIZE); memcpy(ses.session_id, ses.hash, SHA1_HASH_SIZE); } /* we can start creating the kexdh_reply packet */ CHECKCLEARTOWRITE(); buf_putbyte(ses.writepayload, SSH_MSG_KEXDH_REPLY); buf_put_pub_key(ses.writepayload, ses.opts->hostkey, ses.newkeys->algo_hostkey); /* put f */ buf_putmpint(ses.writepayload, &dh_f); mp_clear(&dh_f); /* calc the signature */ buf_put_sign(ses.writepayload, ses.opts->hostkey, ses.newkeys->algo_hostkey, ses.hash, SHA1_HASH_SIZE); /* the SSH_MSG_KEXDH_REPLY is done */ encrypt_packet(); TRACE(("leave send_msg_kexdh_reply")); }
/* Parse pubkey options and set ses.authstate.pubkey_options accordingly. * Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */ int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filename) { int ret = DROPBEAR_FAILURE; TRACE(("enter addpubkeyoptions")) ses.authstate.pubkey_options = (struct PubKeyOptions*)m_malloc(sizeof( struct PubKeyOptions )); buf_setpos(options_buf, 0); while (options_buf->pos < options_buf->len) { if (match_option(options_buf, "no-port-forwarding") == DROPBEAR_SUCCESS) { dropbear_log(LOG_WARNING, "Port forwarding disabled."); ses.authstate.pubkey_options->no_port_forwarding_flag = 1; goto next_option; } #ifdef ENABLE_SVR_AGENTFWD if (match_option(options_buf, "no-agent-forwarding") == DROPBEAR_SUCCESS) { dropbear_log(LOG_WARNING, "Agent forwarding disabled."); ses.authstate.pubkey_options->no_agent_forwarding_flag = 1; goto next_option; } #endif #ifdef ENABLE_X11FWD if (match_option(options_buf, "no-X11-forwarding") == DROPBEAR_SUCCESS) { dropbear_log(LOG_WARNING, "X11 forwarding disabled."); ses.authstate.pubkey_options->no_x11_forwarding_flag = 1; goto next_option; } #endif if (match_option(options_buf, "no-pty") == DROPBEAR_SUCCESS) { dropbear_log(LOG_WARNING, "Pty allocation disabled."); ses.authstate.pubkey_options->no_pty_flag = 1; goto next_option; } if (match_option(options_buf, "command=\"") == DROPBEAR_SUCCESS) { int escaped = 0; const unsigned char* command_start = buf_getptr(options_buf, 0); while (options_buf->pos < options_buf->len) { const char c = buf_getbyte(options_buf); if (!escaped && c == '"') { const int command_len = buf_getptr(options_buf, 0) - command_start; ses.authstate.pubkey_options->forced_command = m_malloc(command_len); memcpy(ses.authstate.pubkey_options->forced_command, command_start, command_len-1); ses.authstate.pubkey_options->forced_command[command_len-1] = '\0'; dropbear_log(LOG_WARNING, "Forced command '%s'", ses.authstate.pubkey_options->forced_command); goto next_option; } escaped = (!escaped && c == '\\'); } dropbear_log(LOG_WARNING, "Badly formatted command= authorized_keys option"); goto bad_option; } next_option: /* * Skip the comma, and move to the next option * (or break out if there are no more). */ if (options_buf->pos < options_buf->len && buf_getbyte(options_buf) != ',') { goto bad_option; } /* Process the next option. */ } /* parsed all options with no problem */ ret = DROPBEAR_SUCCESS; goto end; bad_option: ret = DROPBEAR_FAILURE; m_free(ses.authstate.pubkey_options); ses.authstate.pubkey_options = NULL; dropbear_log(LOG_WARNING, "Bad public key options at %s:%d", filename, line_num); end: TRACE(("leave addpubkeyoptions")) return ret; }
/* Handle a diffie-hellman key exchange reply. */ void recv_msg_kexdh_reply() { sign_key *hostkey = NULL; unsigned int type, keybloblen; unsigned char* keyblob = NULL; TRACE(("enter recv_msg_kexdh_reply")) if (cli_ses.kex_state != KEXDH_INIT_SENT) { dropbear_exit("Received out-of-order kexdhreply"); } type = ses.newkeys->algo_hostkey; TRACE(("type is %d", type)) hostkey = new_sign_key(); 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")) dropbear_exit("Bad KEX packet"); } switch (ses.newkeys->algo_kex->mode) { case DROPBEAR_KEX_NORMAL_DH: { DEF_MP_INT(dh_f); m_mp_init(&dh_f); if (buf_getmpint(ses.payload, &dh_f) != DROPBEAR_SUCCESS) { TRACE(("failed getting mpint")) dropbear_exit("Bad KEX packet"); } kexdh_comb_key(cli_ses.dh_param, &dh_f, hostkey); mp_clear(&dh_f); } break; case DROPBEAR_KEX_ECDH: #ifdef DROPBEAR_ECDH { buffer *ecdh_qs = buf_getstringbuf(ses.payload); kexecdh_comb_key(cli_ses.ecdh_param, ecdh_qs, hostkey); buf_free(ecdh_qs); } #endif break; #ifdef DROPBEAR_CURVE25519 case DROPBEAR_KEX_CURVE25519: { buffer *ecdh_qs = buf_getstringbuf(ses.payload); kexcurve25519_comb_key(cli_ses.curve25519_param, ecdh_qs, hostkey); buf_free(ecdh_qs); } #endif break; } if (cli_ses.dh_param) { free_kexdh_param(cli_ses.dh_param); cli_ses.dh_param = NULL; } #ifdef DROPBEAR_ECDH if (cli_ses.ecdh_param) { free_kexecdh_param(cli_ses.ecdh_param); cli_ses.ecdh_param = NULL; } #endif #ifdef DROPBEAR_CURVE25519 if (cli_ses.curve25519_param) { free_kexcurve25519_param(cli_ses.curve25519_param); cli_ses.curve25519_param = NULL; } #endif cli_ses.param_kex_algo = NULL; if (buf_verify(ses.payload, hostkey, ses.hash) != DROPBEAR_SUCCESS) { dropbear_exit("Bad hostkey signature"); } sign_key_free(hostkey); hostkey = NULL; send_msg_newkeys(); ses.requirenext[0] = SSH_MSG_NEWKEYS; ses.requirenext[1] = 0; TRACE(("leave recv_msg_kexdh_init")) }