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

Reply via email to