aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/libssh/packet.h2
-rw-r--r--src/packet.c168
-rw-r--r--src/packet_cb.c90
-rw-r--r--src/server.c32
-rw-r--r--tests/unittests/torture_packet.c1
5 files changed, 176 insertions, 117 deletions
diff --git a/include/libssh/packet.h b/include/libssh/packet.h
index 6334111b..2328cc5b 100644
--- a/include/libssh/packet.h
+++ b/include/libssh/packet.h
@@ -83,6 +83,8 @@ unsigned char *ssh_packet_encrypt(ssh_session session,
unsigned int len);
int ssh_packet_hmac_verify(ssh_session session,ssh_buffer buffer,
unsigned char *mac, enum ssh_hmac_e type);
+int ssh_packet_set_newkeys(ssh_session session,
+ enum ssh_crypto_direction_e direction);
struct ssh_crypto_struct *ssh_packet_get_current_crypto(ssh_session session,
enum ssh_crypto_direction_e direction);
diff --git a/src/packet.c b/src/packet.c
index ba7687e3..c0e111f1 100644
--- a/src/packet.c
+++ b/src/packet.c
@@ -48,6 +48,7 @@
#include "libssh/auth.h"
#include "libssh/gssapi.h"
#include "libssh/bytearray.h"
+#include "libssh/dh.h"
static ssh_packet_callback default_packet_handlers[]= {
ssh_packet_disconnect_callback, // SSH2_MSG_DISCONNECT 1
@@ -936,7 +937,21 @@ struct ssh_crypto_struct *
ssh_packet_get_current_crypto(ssh_session session,
enum ssh_crypto_direction_e direction)
{
- return session->current_crypto;
+ if (session == NULL) {
+ return NULL;
+ }
+
+ if (session->current_crypto != NULL &&
+ session->current_crypto->used & direction) {
+ return session->current_crypto;
+ }
+
+ if (session->next_crypto != NULL &&
+ session->next_crypto->used & direction) {
+ return session->next_crypto;
+ }
+
+ return NULL;
}
/* in nonblocking mode, socket_read will read as much as it can, and return */
@@ -1431,6 +1446,7 @@ static int packet_send2(ssh_session session)
uint8_t padding_size;
uint32_t finallen, payloadsize, compsize;
uint8_t header[5] = {0};
+ uint8_t type, *payload;
int rc = SSH_ERROR;
crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT);
@@ -1442,6 +1458,9 @@ static int packet_send2(ssh_session session)
hmac_type = session->next_crypto->out_hmac;
}
+ payload = (uint8_t *)ssh_buffer_get(session->out_buffer);
+ type = payload[0]; /* type is the first byte of the packet now */
+
payloadsize = currentlen;
#ifdef WITH_ZLIB
if (crypto != NULL && crypto->do_compress_out &&
@@ -1531,8 +1550,17 @@ static int packet_send2(ssh_session session)
rc = ssh_buffer_reinit(session->out_buffer);
if (rc < 0) {
rc = SSH_ERROR;
+ goto error;
}
+ /* We sent the NEWKEYS so any further packet needs to be encrypted
+ * with the new keys. We can not switch both directions (need to decrypt
+ * peer NEWKEYS) and we do not want to wait for the peer NEWKEYS
+ * too, so we will switch only the OUT direction now.
+ */
+ if (type == SSH2_MSG_NEWKEYS) {
+ rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_OUT);
+ }
error:
return rc; /* SSH_OK, AGAIN or ERROR */
}
@@ -1540,3 +1568,141 @@ error:
int ssh_packet_send(ssh_session session) {
return packet_send2(session);
}
+
+static void
+ssh_init_rekey_state(struct ssh_session_struct *session,
+ struct ssh_cipher_struct *cipher)
+{
+ /* Reset the counters: should be NOOP */
+ cipher->packets = 0;
+ cipher->blocks = 0;
+
+ /* Default rekey limits for ciphers as specified in RFC4344, Section 3.2 */
+ if (cipher->blocksize >= 16) {
+ /* For larger block size (L bits) use maximum of 2**(L/4) blocks */
+ cipher->max_blocks = (uint64_t)1 << (cipher->blocksize*2);
+ } else {
+ /* For smaller blocks use limit of 1 GB as recommended in RFC4253 */
+ cipher->max_blocks = ((uint64_t)1 << 30) / cipher->blocksize;
+ }
+ /* If we have limit provided by user, use the smaller one */
+ if (session->opts.rekey_data != 0) {
+ cipher->max_blocks = MIN(cipher->max_blocks,
+ session->opts.rekey_data / cipher->blocksize);
+ }
+
+ SSH_LOG(SSH_LOG_PROTOCOL,
+ "Set rekey after %" PRIu64 " blocks",
+ cipher->max_blocks);
+}
+
+/*
+ * Once we got SSH2_MSG_NEWKEYS we can switch next_crypto and
+ * current_crypto for our desired direction
+ */
+int
+ssh_packet_set_newkeys(ssh_session session,
+ enum ssh_crypto_direction_e direction)
+{
+ int rc;
+
+ SSH_LOG(SSH_LOG_TRACE,
+ "called, direction =%s%s",
+ direction & SSH_DIRECTION_IN ? " IN " : "",
+ direction & SSH_DIRECTION_OUT ? " OUT " : "");
+
+ session->next_crypto->used |= direction;
+ if (session->current_crypto != NULL) {
+ if (session->current_crypto->used & direction) {
+ SSH_LOG(SSH_LOG_WARNING, "This direction isn't used anymore.");
+ }
+ /* Mark the current requested direction unused */
+ session->current_crypto->used &= ~direction;
+ }
+
+ /* Both sides switched: do the actual switch now */
+ if (session->next_crypto->used == SSH_DIRECTION_BOTH) {
+ size_t digest_len;
+
+ if (session->current_crypto != NULL) {
+ crypto_free(session->current_crypto);
+ session->current_crypto = NULL;
+ }
+
+ session->current_crypto = session->next_crypto;
+ session->current_crypto->used = SSH_DIRECTION_BOTH;
+
+ /* Initialize the next_crypto structure */
+ session->next_crypto = crypto_new();
+ if (session->next_crypto == NULL) {
+ ssh_set_error_oom(session);
+ return SSH_ERROR;
+ }
+
+ digest_len = session->current_crypto->digest_len;
+ session->next_crypto->session_id = malloc(digest_len);
+ if (session->next_crypto->session_id == NULL) {
+ ssh_set_error_oom(session);
+ return SSH_ERROR;
+ }
+
+ memcpy(session->next_crypto->session_id,
+ session->current_crypto->session_id,
+ digest_len);
+
+ return SSH_OK;
+ }
+
+ /* Initialize common structures so the next context can be used in
+ * either direction */
+ if (session->client) {
+ /* The server has this part already done */
+ rc = ssh_make_sessionid(session);
+ if (rc != SSH_OK) {
+ return SSH_ERROR;
+ }
+
+ /*
+ * Set the cryptographic functions for the next crypto
+ * (it is needed for ssh_generate_session_keys for key lengths)
+ */
+ rc = crypt_set_algorithms_client(session);
+ if (rc < 0) {
+ return SSH_ERROR;
+ }
+ }
+
+ if (ssh_generate_session_keys(session) < 0) {
+ return SSH_ERROR;
+ }
+
+ /* Initialize rekeying states */
+ ssh_init_rekey_state(session,
+ session->next_crypto->out_cipher);
+ ssh_init_rekey_state(session,
+ session->next_crypto->in_cipher);
+ if (session->opts.rekey_time != 0) {
+ ssh_timestamp_init(&session->last_rekey_time);
+ SSH_LOG(SSH_LOG_PROTOCOL, "Set rekey after %" PRIu32 " seconds",
+ session->opts.rekey_time/1000);
+ }
+
+ /* Initialize the encryption and decryption keys in next_crypto */
+ rc = session->next_crypto->in_cipher->set_decrypt_key(
+ session->next_crypto->in_cipher,
+ session->next_crypto->decryptkey,
+ session->next_crypto->decryptIV);
+ if (rc < 0) {
+ return SSH_ERROR;
+ }
+
+ rc = session->next_crypto->out_cipher->set_encrypt_key(
+ session->next_crypto->out_cipher,
+ session->next_crypto->encryptkey,
+ session->next_crypto->encryptIV);
+ if (rc < 0) {
+ return SSH_ERROR;
+ }
+
+ return SSH_OK;
+}
diff --git a/src/packet_cb.c b/src/packet_cb.c
index 03b2b961..43dae481 100644
--- a/src/packet_cb.c
+++ b/src/packet_cb.c
@@ -136,33 +136,6 @@ error:
return SSH_PACKET_USED;
}
-static void
-ssh_init_rekey_state(struct ssh_session_struct *session,
- struct ssh_cipher_struct *cipher)
-{
- /* Reset the counters: should be NOOP */
- cipher->packets = 0;
- cipher->blocks = 0;
-
- /* Default rekey limits for ciphers as specified in RFC4344, Section 3.2 */
- if (cipher->blocksize >= 16) {
- /* For larger block size (L bits) use maximum of 2**(L/4) blocks */
- cipher->max_blocks = (uint64_t)1 << (cipher->blocksize*2);
- } else {
- /* For smaller blocks use limit of 1 GB as recommended in RFC4253 */
- cipher->max_blocks = ((uint64_t)1 << 30) / cipher->blocksize;
- }
- /* If we have limit provided by user, use the smaller one */
- if (session->opts.rekey_data != 0) {
- cipher->max_blocks = MIN(cipher->max_blocks,
- session->opts.rekey_data / cipher->blocksize);
- }
-
- SSH_LOG(SSH_LOG_PROTOCOL,
- "Set rekey after %" PRIu64 " blocks",
- cipher->max_blocks);
-}
-
SSH_PACKET_CALLBACK(ssh_packet_newkeys){
ssh_string sig_blob = NULL;
ssh_signature sig = NULL;
@@ -188,23 +161,6 @@ SSH_PACKET_CALLBACK(ssh_packet_newkeys){
ssh_key server_key;
/* client */
- rc = ssh_make_sessionid(session);
- if (rc != SSH_OK) {
- goto error;
- }
-
- /*
- * Set the cryptographic functions for the next crypto
- * (it is needed for ssh_generate_session_keys for key lengths)
- */
- rc = crypt_set_algorithms_client(session);
- if (rc < 0) {
- goto error;
- }
-
- if (ssh_generate_session_keys(session) < 0) {
- goto error;
- }
/* Verify the host's signature. FIXME do it sooner */
sig_blob = session->next_crypto->dh_server_signature;
@@ -249,48 +205,10 @@ SSH_PACKET_CALLBACK(ssh_packet_newkeys){
}
SSH_LOG(SSH_LOG_PROTOCOL,"Signature verified and valid");
- /*
- * Once we got SSH2_MSG_NEWKEYS we can switch next_crypto and
- * current_crypto
- */
- if (session->current_crypto) {
- crypto_free(session->current_crypto);
- session->current_crypto=NULL;
- }
-
- /* FIXME later, include a function to change keys */
- session->current_crypto = session->next_crypto;
-
- /* Initialize rekeying states */
- ssh_init_rekey_state(session,
- session->current_crypto->out_cipher);
- ssh_init_rekey_state(session,
- session->current_crypto->in_cipher);
- if (session->opts.rekey_time != 0) {
- ssh_timestamp_init(&session->last_rekey_time);
- SSH_LOG(SSH_LOG_PROTOCOL, "Set rekey after %" PRIu32 " seconds",
- session->opts.rekey_time/1000);
- }
-
- session->next_crypto = crypto_new();
- if (session->next_crypto == NULL) {
- ssh_set_error_oom(session);
- goto error;
- }
- session->next_crypto->session_id = malloc(session->current_crypto->digest_len);
- if (session->next_crypto->session_id == NULL) {
- ssh_set_error_oom(session);
- goto error;
- }
- memcpy(session->next_crypto->session_id, session->current_crypto->session_id,
- session->current_crypto->digest_len);
- if (session->current_crypto->in_cipher->set_decrypt_key(session->current_crypto->in_cipher, session->current_crypto->decryptkey,
- session->current_crypto->decryptIV) < 0) {
- goto error;
- }
- if (session->current_crypto->out_cipher->set_encrypt_key(session->current_crypto->out_cipher, session->current_crypto->encryptkey,
- session->current_crypto->encryptIV) < 0) {
- goto error;
+ /* When receiving this packet, we switch on the incomming crypto. */
+ rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN);
+ if (rc != SSH_OK) {
+ goto error;
}
}
session->dh_handshake_state = DH_STATE_FINISHED;
diff --git a/src/server.c b/src/server.c
index 5702e5c9..78e5ece6 100644
--- a/src/server.c
+++ b/src/server.c
@@ -484,37 +484,9 @@ static void ssh_server_connection_callback(ssh_session session){
break;
case SSH_SESSION_STATE_DH:
if(session->dh_handshake_state==DH_STATE_FINISHED){
- if (ssh_generate_session_keys(session) < 0) {
- goto error;
- }
-
- /*
- * Once we got SSH2_MSG_NEWKEYS we can switch next_crypto and
- * current_crypto
- */
- if (session->current_crypto) {
- crypto_free(session->current_crypto);
- }
- /* FIXME TODO later, include a function to change keys */
- session->current_crypto = session->next_crypto;
- session->next_crypto = crypto_new();
- if (session->next_crypto == NULL) {
- goto error;
- }
- session->next_crypto->session_id = malloc(session->current_crypto->digest_len);
- if (session->next_crypto->session_id == NULL) {
- ssh_set_error_oom(session);
- goto error;
- }
- memcpy(session->next_crypto->session_id, session->current_crypto->session_id,
- session->current_crypto->digest_len);
- if (session->current_crypto->in_cipher->set_decrypt_key(session->current_crypto->in_cipher, session->current_crypto->decryptkey,
- session->current_crypto->decryptIV) < 0) {
- goto error;
- }
- if (session->current_crypto->out_cipher->set_encrypt_key(session->current_crypto->out_cipher, session->current_crypto->encryptkey,
- session->current_crypto->encryptIV) < 0) {
+ rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN);
+ if (rc != SSH_OK) {
goto error;
}
diff --git a/tests/unittests/torture_packet.c b/tests/unittests/torture_packet.c
index 24735efc..32d91b61 100644
--- a/tests/unittests/torture_packet.c
+++ b/tests/unittests/torture_packet.c
@@ -102,6 +102,7 @@ torture_packet(const char *cipher, const char *mac_type,
rc = out_cipher->set_encrypt_key(out_cipher,
session->current_crypto->encryptkey,
session->current_crypto->encryptIV);
+ session->current_crypto->used = SSH_DIRECTION_BOTH;
assert_int_equal(rc, SSH_OK);
assert_non_null(session->out_buffer);