Ensure SSL3_FLAGS_CCS_OK (or d1->change_cipher_spec_ok for DTLS) is reset
authorEmilia Kasper <emilia@openssl.org>
Wed, 19 Nov 2014 16:01:36 +0000 (17:01 +0100)
committerEmilia Kasper <emilia@openssl.org>
Thu, 20 Nov 2014 14:32:08 +0000 (15:32 +0100)
once the ChangeCipherSpec message is received. Previously, the server would
set the flag once at SSL3_ST_SR_CERT_VRFY and again at SSL3_ST_SR_FINISHED.
This would allow a second CCS to arrive and would corrupt the server state.

(Because the first CCS would latch the correct keys and subsequent CCS
messages would have to be encrypted, a MitM attacker cannot exploit this,
though.)

Thanks to Joeri de Ruiter for reporting this issue.

Reviewed-by: Matt Caswell <matt@openssl.org>
(cherry picked from commit e94a6c0ede623960728415b68650a595e48f5a43)

Conflicts:
CHANGES
ssl/s3_srvr.c

CHANGES
ssl/d1_clnt.c
ssl/d1_srvr.c
ssl/dtls1.h
ssl/s3_clnt.c
ssl/s3_srvr.c
ssl/ssl3.h
ssl/t1_lib.c

diff --git a/CHANGES b/CHANGES
index 38c4e5f1d703b2e5dc2602765b427f75ed907281..204262c022c7cdbdd7630d114388ce80f3b6c3d7 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -4,6 +4,11 @@
 
  Changes between 1.0.1j and 1.0.1k [xx XXX xxxx]
 
+   *) Tighten handling of the ChangeCipherSpec (CCS) message: reject
+      early CCS messages during renegotiation. (Note that because
+      renegotiation is encrypted, this early CCS was not exploitable.)
+      [Emilia Käsper]
+
    *) Tighten client-side session ticket handling during renegotiation:
       ensure that the client only accepts a session ticket if the server sends
       the extension anew in the ServerHello. Previously, a TLS client would
index 00e14b53c7f11ec1e7ce1e01b8317a69a8aea856..9947be22ffc866ebee06c84c8f033c4438ffd837 100644 (file)
@@ -249,6 +249,9 @@ int dtls1_connect(SSL *s)
                        memset(s->s3->client_random,0,sizeof(s->s3->client_random));
                        s->d1->send_cookie = 0;
                        s->hit = 0;
+                       s->d1->change_cipher_spec_ok = 0;
+                       /* Should have been reset by ssl3_get_finished, too. */
+                       s->s3->change_cipher_spec = 0;
                        break;
 
 #ifndef OPENSSL_NO_SCTP
@@ -492,7 +495,6 @@ int dtls1_connect(SSL *s)
                                else
 #endif
                                        s->state=SSL3_ST_CW_CHANGE_A;
-                               s->s3->change_cipher_spec=0;
                                }
 
                        s->init_num=0;
@@ -513,7 +515,6 @@ int dtls1_connect(SSL *s)
 #endif
                                s->state=SSL3_ST_CW_CHANGE_A;
                        s->init_num=0;
-                       s->s3->change_cipher_spec=0;
                        break;
 
                case SSL3_ST_CW_CHANGE_A:
index 4b8ba3e452f36cd0b64b0a1da425ca18de182659..6a075b33969f9e0844ffbbf2d5a70030107b4774 100644 (file)
@@ -246,6 +246,9 @@ int dtls1_accept(SSL *s)
                                }
 
                        s->init_num=0;
+                       s->d1->change_cipher_spec_ok = 0;
+                       /* Should have been reset by ssl3_get_finished, too. */
+                       s->s3->change_cipher_spec = 0;
 
                        if (s->state != SSL_ST_RENEGOTIATE)
                                {
@@ -658,8 +661,14 @@ int dtls1_accept(SSL *s)
 
                case SSL3_ST_SR_CERT_VRFY_A:
                case SSL3_ST_SR_CERT_VRFY_B:
-
-                       s->d1->change_cipher_spec_ok = 1;
+                       /*
+                        * This *should* be the first time we enable CCS, but be
+                        * extra careful about surrounding code changes. We need
+                        * to set this here because we don't know if we're
+                        * expecting a CertificateVerify or not.
+                        */
+                       if (!s->s3->change_cipher_spec)
+                               s->d1->change_cipher_spec_ok = 1;
                        /* we should decide if we expected this one */
                        ret=ssl3_get_cert_verify(s);
                        if (ret <= 0) goto end;
@@ -675,7 +684,18 @@ int dtls1_accept(SSL *s)
 
                case SSL3_ST_SR_FINISHED_A:
                case SSL3_ST_SR_FINISHED_B:
-                       s->d1->change_cipher_spec_ok = 1;
+                       /*
+                        * Enable CCS for resumed handshakes.
+                        * In a full handshake, we end up here through
+                        * SSL3_ST_SR_CERT_VRFY_B, so change_cipher_spec_ok was
+                        * already set. Receiving a CCS clears the flag, so make
+                        * sure not to re-enable it to ban duplicates.
+                        * s->s3->change_cipher_spec is set when a CCS is
+                        * processed in d1_pkt.c, and remains set until
+                        * the client's Finished message is read.
+                        */
+                       if (!s->s3->change_cipher_spec)
+                               s->d1->change_cipher_spec_ok = 1;
                        ret=ssl3_get_finished(s,SSL3_ST_SR_FINISHED_A,
                                SSL3_ST_SR_FINISHED_B);
                        if (ret <= 0) goto end;
index 192c5deff98a0ffd970aafc571613666a777367a..3deffbf1af75c8387a75a8a03e68e810f1473bf5 100644 (file)
@@ -252,6 +252,10 @@ typedef struct dtls1_state_st
        unsigned int handshake_fragment_len;
 
        unsigned int retransmitting;
+       /*
+        * Set when the handshake is ready to process peer's ChangeCipherSpec message.
+        * Cleared after the message has been processed.
+        */
        unsigned int change_cipher_spec_ok;
 
 #ifndef OPENSSL_NO_SCTP
index 6bbcf391c846ef9b4844eda6b3eb2485d0b4cf08..7ad13579adac0e7451aa0f544e028efbeb97fe4f 100644 (file)
@@ -273,6 +273,9 @@ int ssl3_connect(SSL *s)
                        s->state=SSL3_ST_CW_CLNT_HELLO_A;
                        s->ctx->stats.sess_connect++;
                        s->init_num=0;
+                       s->s3->flags &= ~SSL3_FLAGS_CCS_OK;
+                       /* Should have been reset by ssl3_get_finished, too. */
+                       s->s3->change_cipher_spec = 0;
                        break;
 
                case SSL3_ST_CW_CLNT_HELLO_A:
@@ -420,12 +423,10 @@ int ssl3_connect(SSL *s)
                        else
                                {
                                s->state=SSL3_ST_CW_CHANGE_A;
-                               s->s3->change_cipher_spec=0;
                                }
                        if (s->s3->flags & TLS1_FLAGS_SKIP_CERT_VERIFY)
                                {
                                s->state=SSL3_ST_CW_CHANGE_A;
-                               s->s3->change_cipher_spec=0;
                                }
 
                        s->init_num=0;
@@ -437,7 +438,6 @@ int ssl3_connect(SSL *s)
                        if (ret <= 0) goto end;
                        s->state=SSL3_ST_CW_CHANGE_A;
                        s->init_num=0;
-                       s->s3->change_cipher_spec=0;
                        break;
 
                case SSL3_ST_CW_CHANGE_A:
@@ -497,7 +497,6 @@ int ssl3_connect(SSL *s)
                                s->method->ssl3_enc->client_finished_label,
                                s->method->ssl3_enc->client_finished_label_len);
                        if (ret <= 0) goto end;
-                       s->s3->flags |= SSL3_FLAGS_CCS_OK;
                        s->state=SSL3_ST_CW_FLUSH;
 
                        /* clear flags */
@@ -546,7 +545,6 @@ int ssl3_connect(SSL *s)
 
                case SSL3_ST_CR_FINISHED_A:
                case SSL3_ST_CR_FINISHED_B:
-
                        s->s3->flags |= SSL3_FLAGS_CCS_OK;
                        ret=ssl3_get_finished(s,SSL3_ST_CR_FINISHED_A,
                                SSL3_ST_CR_FINISHED_B);
@@ -888,7 +886,6 @@ int ssl3_get_server_hello(SSL *s)
                        s->session->cipher = pref_cipher ?
                                pref_cipher : ssl_get_cipher_by_char(s, p+j);
                        s->hit = 1;
-                       s->s3->flags |= SSL3_FLAGS_CCS_OK;
                        }
                }
 #endif /* OPENSSL_NO_TLSEXT */
@@ -904,7 +901,6 @@ int ssl3_get_server_hello(SSL *s)
                SSLerr(SSL_F_SSL3_GET_SERVER_HELLO,SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT);
                goto f_err;
                }
-           s->s3->flags |= SSL3_FLAGS_CCS_OK;
            s->hit=1;
            }
        /* a miss or crap from the other end */
index 9ac7e8ad31be816edf9e58fe73e8dcbbdc307607..e71089840242e11a8c3cd6ab41db8e61f46e284b 100644 (file)
@@ -300,6 +300,9 @@ int ssl3_accept(SSL *s)
 
                        s->init_num=0;
                        s->s3->flags &= ~SSL3_FLAGS_SGC_RESTART_DONE;
+                       s->s3->flags &= ~SSL3_FLAGS_CCS_OK;
+                       /* Should have been reset by ssl3_get_finished, too. */
+                       s->s3->change_cipher_spec = 0;
 
                        if (s->state != SSL_ST_RENEGOTIATE)
                                {
@@ -676,8 +679,14 @@ int ssl3_accept(SSL *s)
 
                case SSL3_ST_SR_CERT_VRFY_A:
                case SSL3_ST_SR_CERT_VRFY_B:
-
-                       s->s3->flags |= SSL3_FLAGS_CCS_OK;
+                       /*
+                        * This *should* be the first time we enable CCS, but be
+                        * extra careful about surrounding code changes. We need
+                        * to set this here because we don't know if we're
+                        * expecting a CertificateVerify or not.
+                        */
+                       if (!s->s3->change_cipher_spec)
+                               s->s3->flags |= SSL3_FLAGS_CCS_OK;
                        /* we should decide if we expected this one */
                        ret=ssl3_get_cert_verify(s);
                        if (ret <= 0) goto end;
@@ -696,6 +705,19 @@ int ssl3_accept(SSL *s)
 #if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
                case SSL3_ST_SR_NEXT_PROTO_A:
                case SSL3_ST_SR_NEXT_PROTO_B:
+                       /*
+                        * Enable CCS for resumed handshakes with NPN.
+                        * In a full handshake with NPN, we end up here through
+                        * SSL3_ST_SR_CERT_VRFY_B, where SSL3_FLAGS_CCS_OK was
+                        * already set. Receiving a CCS clears the flag, so make
+                        * sure not to re-enable it to ban duplicates.
+                        * s->s3->change_cipher_spec is set when a CCS is
+                        * processed in s3_pkt.c, and remains set until
+                        * the client's Finished message is read.
+                        */
+                       if (!s->s3->change_cipher_spec)
+                               s->s3->flags |= SSL3_FLAGS_CCS_OK;
+
                        ret=ssl3_get_next_proto(s);
                        if (ret <= 0) goto end;
                        s->init_num = 0;
@@ -705,7 +727,18 @@ int ssl3_accept(SSL *s)
 
                case SSL3_ST_SR_FINISHED_A:
                case SSL3_ST_SR_FINISHED_B:
-                       s->s3->flags |= SSL3_FLAGS_CCS_OK;
+                       /*
+                        * Enable CCS for resumed handshakes without NPN.
+                        * In a full handshake, we end up here through
+                        * SSL3_ST_SR_CERT_VRFY_B, where SSL3_FLAGS_CCS_OK was
+                        * already set. Receiving a CCS clears the flag, so make
+                        * sure not to re-enable it to ban duplicates.
+                        * s->s3->change_cipher_spec is set when a CCS is
+                        * processed in s3_pkt.c, and remains set until
+                        * the client's Finished message is read.
+                        */
+                       if (!s->s3->change_cipher_spec)
+                               s->s3->flags |= SSL3_FLAGS_CCS_OK;
                        ret=ssl3_get_finished(s,SSL3_ST_SR_FINISHED_A,
                                SSL3_ST_SR_FINISHED_B);
                        if (ret <= 0) goto end;
@@ -777,7 +810,6 @@ int ssl3_accept(SSL *s)
 #else
                                if (s->s3->next_proto_neg_seen)
                                        {
-                                       s->s3->flags |= SSL3_FLAGS_CCS_OK;
                                        s->s3->tmp.next_state=SSL3_ST_SR_NEXT_PROTO_A;
                                        }
                                else
index 85f150409d21de364149549944e0707b02fee1dc..6fad054e03c5e26d0b08ed81279469c0c7d360e2 100644 (file)
@@ -393,8 +393,12 @@ typedef struct ssl3_buffer_st
 #define TLS1_FLAGS_TLS_PADDING_BUG             0x0008
 #define TLS1_FLAGS_SKIP_CERT_VERIFY            0x0010
 #define TLS1_FLAGS_KEEP_HANDSHAKE              0x0020
+/*
+ * Set when the handshake is ready to process peer's ChangeCipherSpec message.
+ * Cleared after the message has been processed.
+ */
 #define SSL3_FLAGS_CCS_OK                      0x0080
+
 /* SSL3_FLAGS_SGC_RESTART_DONE is set when we
  * restart a handshake because of MS SGC and so prevents us
  * from restarting the handshake in a loop. It's reset on a
@@ -456,8 +460,11 @@ typedef struct ssl3_state_st
         * and freed and MD_CTX-es for all required digests are stored in
         * this array */
        EVP_MD_CTX **handshake_dgst;
-       /* this is set whenerver we see a change_cipher_spec message
-        * come in when we are not looking for one */
+       /*
+        * Set whenever an expected ChangeCipherSpec message is processed.
+        * Unset when the peer's Finished message is received.
+        * Unexpected ChangeCipherSpec messages trigger a fatal alert.
+        */
        int change_cipher_spec;
 
        int warn_alert;
index c7032f717d403082eb77aaf46b87663c27f841b3..c91489da6f728a765721b5a501d324b9f513ce6c 100644 (file)
@@ -1513,7 +1513,7 @@ int ssl_parse_serverhello_tlsext(SSL *s, unsigned char **p, unsigned char *d, in
 #ifndef OPENSSL_NO_NEXTPROTONEG
        s->s3->next_proto_neg_seen = 0;
 #endif
-        s->tlsext_ticket_expected = 0;
+       s->tlsext_ticket_expected = 0;
 
 #ifndef OPENSSL_NO_HEARTBEATS
        s->tlsext_heartbeat &= ~(SSL_TLSEXT_HB_ENABLED |