Unfortunately the problem was way more severe than I expected:
This patch solves four issues now. At first the index of the
retransmission queue is the message sequence number bitwise ORed with
the current epoch. The epoch cannot be used to retrieve the buffered
messages because it could have changed until a retransmission has to
be done and is therefor not necessarily known. Since there can only be
one handshake at a time, every message can only be once in the queue
so the epoch can be removed. The message sequence number alone is not
sufficient, because the ChangeCipherSpec doesn't have one, so it will
have the same index as the Finished. A solution is using the message
sequence number multiplied by 2 and if the message is a
ChangeCipherSpec 1 is subtraced. This has the advantage that the index
of the CCS is always one less than the Finished, so the order of the
indices is preserved, which is important in a priority queue and the
unsigned short sequence number variable can be kept.
The second issue is when the server's CCS/Finished flight got lost and
the client repeats its last flight, the server already left the
handshake. Because of the unexpected messages it will enter the
handshake again which fails because then a ClientHello is expected.
This can be solved by adding another condition for unexpected
messages. If a Finished is received with the current epoch, it only
can be a retransmission, so in that case the last flight of the server
is just repeated without entering the handshake routine.
The third issue is that retransmissions are performed with the current
session parameters, that is epoch, algorithms and mastersecret. This
fails if a ChangeCiperSpec got lost and handshake messages of the
previous epoch have to be repeated. As a solution the current state is
saved with the message in the retransmission queue, to be able to
restore it when a retransmission is necessary.
Additionally the sequence number of the record layer is now bound to
the epoch. Retransmissions of a previous epoch will reuse the sequence
number of that epoch instead of the current one (which has been reset
after sending the CCS). Otherwise there can be combinations which
already have been there and the peer will discard the messages in its
replay check.
--- ssl/d1_both.c 2007-10-17 23:17:49.000000000 +0200
+++ ssl/d1_both.c 2009-02-25 16:37:55.000000000 +0100
@@ -136,7 +136,6 @@
static void dtls1_set_message_header_int(SSL *s, unsigned char mt,
unsigned long len, unsigned short seq_num, unsigned long frag_off,
unsigned long frag_len);
-static int dtls1_retransmit_buffered_messages(SSL *s);
static long dtls1_get_message_fragment(SSL *s, int st1, int stn,
long max, int *ok);
@@ -943,7 +942,7 @@
}
-static int
+int
dtls1_retransmit_buffered_messages(SSL *s)
{
pqueue sent = s->d1->sent_messages;
@@ -957,8 +956,8 @@
for ( item = pqueue_next(&iter); item != NULL; item =
pqueue_next(&iter))
{
frag = (hm_fragment *)item->data;
- if ( dtls1_retransmit_message(s, frag->msg_header.seq, 0, &found)
<= 0 &&
- found)
+ if ( dtls1_retransmit_message(s, frag->msg_header.seq * 2 - frag-
>msg_header.is_ccs,
+ 0, &found) <= 0 && found)
{
fprintf(stderr, "dtls1_retransmit_message() failed\n");
return -1;
@@ -974,7 +973,6 @@
pitem *item;
hm_fragment *frag;
PQ_64BIT seq64;
- unsigned int epoch = s->d1->w_epoch;
/* this function is called immediately after a message has
* been serialized */
@@ -988,7 +986,6 @@
{
OPENSSL_assert(s->d1->w_msg_hdr.msg_len +
DTLS1_CCS_HEADER_LENGTH <= (unsigned int)s->init_num);
- epoch++;
}
else
{
@@ -1003,8 +1000,24 @@
frag->msg_header.frag_len = s->d1->w_msg_hdr.msg_len;
frag->msg_header.is_ccs = is_ccs;
+ /* save current state*/
+ frag->msg_header.saved_retransmit_state.enc_write_ctx = s-
>enc_write_ctx;
+ frag->msg_header.saved_retransmit_state.write_hash = s->write_hash;
+ frag->msg_header.saved_retransmit_state.compress = s->compress;
+ frag->msg_header.saved_retransmit_state.session = s->session;
+ frag->msg_header.saved_retransmit_state.epoch = s->d1->w_epoch;
+
pq_64bit_init(&seq64);
- pq_64bit_assign_word(&seq64, epoch<<16 | frag->msg_header.seq);
+
+ /* The index of the retransmission queue actually is the message
sequence number,
+ * since the queue only contains messages of a single handshake.
However, the
+ * ChangeCipherSpec has no message sequence number and so using only
the sequence
+ * will result in the CCS and Finished having the same index. To
prevent this,
+ * the sequence number is multiplied by 2. In case of a CCS 1 is
subtracted.
+ * This does not only differ CSS and Finished, it also maintains the
order of the
+ * index (important for priority queues) and fits in the unsigned
short variable.
+ */
+ pq_64bit_assign_word(&seq64, frag->msg_header.seq * 2 - frag-
>msg_header.is_ccs);
item = pitem_new(seq64, frag);
pq_64bit_free(&seq64);
@@ -1034,6 +1047,8 @@
hm_fragment *frag ;
unsigned long header_length;
PQ_64BIT seq64;
+ struct dtls1_retransmit_state saved_state;
+ unsigned char save_write_sequence[8];
/*
OPENSSL_assert(s->init_num == 0);
@@ -1069,9 +1084,45 @@
frag->msg_header.msg_len, frag->msg_header.seq, 0,
frag->msg_header.frag_len);
+ /* save current state */
+ saved_state.enc_write_ctx = s->enc_write_ctx;
+ saved_state.write_hash = s->write_hash;
+ saved_state.compress = s->compress;
+ saved_state.session = s->session;
+ saved_state.epoch = s->d1->w_epoch;
+ saved_state.epoch = s->d1->w_epoch;
+
s->d1->retransmitting = 1;
+
+ /* restore state in which the message was originally sent */
+ s->enc_write_ctx = frag-
>msg_header.saved_retransmit_state.enc_write_ctx;
+ s->write_hash = frag->msg_header.saved_retransmit_state.write_hash;
+ s->compress = frag->msg_header.saved_retransmit_state.compress;
+ s->session = frag->msg_header.saved_retransmit_state.session;
+ s->d1->w_epoch = frag->msg_header.saved_retransmit_state.epoch;
+
+ if (frag->msg_header.saved_retransmit_state.epoch ==
saved_state.epoch - 1)
+ {
+ memcpy(save_write_sequence, s->s3->write_sequence, sizeof(s->s3-
>write_sequence));
+ memcpy(s->s3->write_sequence, s->d1->last_write_sequence, sizeof(s-
>s3->write_sequence));
+ }
+
ret = dtls1_do_write(s, frag->msg_header.is_ccs ?
- SSL3_RT_CHANGE_CIPHER_SPEC : SSL3_RT_HANDSHAKE);
+ SSL3_RT_CHANGE_CIPHER_SPEC :
SSL3_RT_HANDSHAKE);
+
+ /* restore current state */
+ s->enc_write_ctx = saved_state.enc_write_ctx;
+ s->write_hash = saved_state.write_hash;
+ s->compress = saved_state.compress;
+ s->session = saved_state.session;
+ s->d1->w_epoch = saved_state.epoch;
+
+ if (frag->msg_header.saved_retransmit_state.epoch ==
saved_state.epoch - 1)
+ {
+ memcpy(s->d1->last_write_sequence, s->s3->write_sequence, sizeof(s-
>s3->write_sequence));
+ memcpy(s->s3->write_sequence, save_write_sequence, sizeof(s->s3-
>write_sequence));
+ }
+
s->d1->retransmitting = 0;
(void)BIO_flush(SSL_get_wbio(s));
--- ssl/d1_pkt.c 2008-10-13 08:43:06.000000000 +0200
+++ ssl/d1_pkt.c 2009-02-25 16:43:18.000000000 +0100
@@ -942,7 +942,7 @@
n2s(p, seq);
n2l3(p, frag_off);
- dtls1_retransmit_message(s, seq, frag_off,
&found);
+ dtls1_retransmit_message(s, seq * 2, frag_off,
&found);
if ( ! found && SSL_in_init(s))
{
/* fprintf( stderr,"in init = %d\n",
SSL_in_init(s)); */
@@ -1035,6 +1035,16 @@
goto start;
}
+ /* If we are server, we may have a repeated FINISHED of the
+ * client here, then retransmit our CCS and FINISHED.
+ */
+ if (msg_hdr.type == SSL3_MT_FINISHED)
+ {
+ dtls1_retransmit_buffered_messages(s);
+ rr->length = 0;
+ goto start;
+ }
+
if (((s->state&SSL_ST_MASK) == SSL_ST_OK) &&
!(s->s3->flags & SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS))
{
@@ -1758,6 +1768,7 @@
else
{
seq = s->s3->write_sequence;
+ memcpy(s->d1->last_write_sequence, seq, sizeof(s->s3-
>write_sequence));
s->d1->w_epoch++;
}
--- ssl/dtls1.h 2008-09-13 20:25:36.000000000 +0200
+++ ssl/dtls1.h 2009-02-25 16:44:50.000000000 +0100
@@ -101,6 +101,19 @@
PQ_64BIT max_seq_num; /* max record number seen so far */
} DTLS1_BITMAP;
+struct dtls1_retransmit_state
+ {
+ EVP_CIPHER_CTX *enc_write_ctx; /* cryptographic state */
+ const EVP_MD *write_hash; /* used for mac generation */
+#ifndef OPENSSL_NO_COMP
+ COMP_CTX *compress; /* compression */
+#else
+ char *compress;
+#endif
+ SSL_SESSION *session;
+ unsigned short epoch;
+ };
+
struct hm_header_st
{
unsigned char type;
@@ -109,6 +122,7 @@
unsigned long frag_off;
unsigned long frag_len;
unsigned int is_ccs;
+ struct dtls1_retransmit_state saved_retransmit_state;
};
struct ccs_header_st
@@ -168,6 +182,9 @@
unsigned short handshake_read_seq;
+ /* save last sequence number for retransmissions */
+ unsigned char last_write_sequence[8];
+
/* Received handshake records (processed and unprocessed) */
record_pqueue unprocessed_rcds;
record_pqueue processed_rcds;
--- ssl/ssl_locl.h 2009-01-05 15:43:07.000000000 +0100
+++ ssl/ssl_locl.h 2009-02-25 10:59:46.000000000 +0100
@@ -862,6 +862,7 @@
int dtls1_buffer_message(SSL *s, int ccs);
int dtls1_retransmit_message(SSL *s, unsigned short seq,
unsigned long frag_off, int *found);
+int dtls1_retransmit_buffered_messages(SSL *s);
void dtls1_clear_record_buffer(SSL *s);
void dtls1_get_message_header(unsigned char *data, struct
hm_header_st *msg_hdr);
void dtls1_get_ccs_header(unsigned char *data, struct ccs_header_st
*ccs_hdr);
______________________________________________________________________
OpenSSL Project http://www.openssl.org
Development Mailing List openssl-dev@openssl.org
Automated List Manager majord...@openssl.org