]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[tls] Support RFC5746 secure renegotiation
authorMichael Brown <mcb30@ipxe.org>
Tue, 4 Jul 2017 11:51:29 +0000 (12:51 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 4 Jul 2017 18:54:34 +0000 (19:54 +0100)
Support renegotiation with servers supporting RFC5746.  This allows
for the use of per-directory client certificates.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/tls.h
src/net/tls.c

index 7d982c32673501e86089e4c79175c55730f8335e..7345fbee472e89d14fe660f9ffaa68309115a524 100644 (file)
@@ -108,6 +108,17 @@ struct tls_header {
 /* TLS signature algorithms extension */
 #define TLS_SIGNATURE_ALGORITHMS 13
 
+/* TLS renegotiation information extension */
+#define TLS_RENEGOTIATION_INFO 0xff01
+
+/** TLS verification data */
+struct tls_verify_data {
+       /** Client verification data */
+       uint8_t client[12];
+       /** Server verification data */
+       uint8_t server[12];
+} __attribute__ (( packed ));
+
 /** TLS RX state machine state */
 enum tls_rx_state {
        TLS_RX_HEADER = 0,
@@ -271,6 +282,10 @@ struct tls_session {
        uint8_t *handshake_ctx;
        /** Client certificate (if used) */
        struct x509_certificate *cert;
+       /** Secure renegotiation flag */
+       int secure_renegotiation;
+       /** Verification data */
+       struct tls_verify_data verify;
 
        /** Server certificate chain */
        struct x509_chain *chain;
index 2b809a628ebb78711a19c72cbe4d96146f957dbb..b197c111f8a121b7be92fec491a2dfe4aa61826a 100644 (file)
@@ -162,6 +162,14 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #define EINFO_EPERM_CLIENT_CERT                                                \
        __einfo_uniqify ( EINFO_EPERM, 0x03,                            \
                          "No suitable client certificate available" )
+#define EPERM_RENEG_INSECURE __einfo_error ( EINFO_EPERM_RENEG_INSECURE )
+#define EINFO_EPERM_RENEG_INSECURE                                     \
+       __einfo_uniqify ( EINFO_EPERM, 0x04,                            \
+                         "Secure renegotiation not supported" )
+#define EPERM_RENEG_VERIFY __einfo_error ( EINFO_EPERM_RENEG_VERIFY )
+#define EINFO_EPERM_RENEG_VERIFY                                       \
+       __einfo_uniqify ( EINFO_EPERM, 0x05,                            \
+                         "Secure renegotiation verification failed" )
 #define EPROTO_VERSION __einfo_error ( EINFO_EPROTO_VERSION )
 #define EINFO_EPROTO_VERSION                                           \
        __einfo_uniqify ( EINFO_EPROTO, 0x01,                           \
@@ -887,6 +895,30 @@ static void tls_verify_handshake ( struct tls_session *tls, void *out ) {
  ******************************************************************************
  */
 
+/**
+ * Restart negotiation
+ *
+ * @v tls              TLS session
+ */
+static void tls_restart ( struct tls_session *tls ) {
+
+       /* Sanity check */
+       assert ( ! tls->tx_pending );
+       assert ( ! is_pending ( &tls->client_negotiation ) );
+       assert ( ! is_pending ( &tls->server_negotiation ) );
+
+       /* (Re)initialise handshake context */
+       digest_init ( &md5_sha1_algorithm, tls->handshake_md5_sha1_ctx );
+       digest_init ( &sha256_algorithm, tls->handshake_sha256_ctx );
+       tls->handshake_digest = &sha256_algorithm;
+       tls->handshake_ctx = tls->handshake_sha256_ctx;
+
+       /* (Re)start negotiation */
+       tls->tx_pending = TLS_TX_CLIENT_HELLO;
+       pending_get ( &tls->client_negotiation );
+       pending_get ( &tls->server_negotiation );
+}
+
 /**
  * Resume TX state machine
  *
@@ -954,6 +986,13 @@ static int tls_send_client_hello ( struct tls_session *tls ) {
                                struct tls_signature_hash_id
                                        code[TLS_NUM_SIG_HASH_ALGORITHMS];
                        } __attribute__ (( packed )) signature_algorithms;
+                       uint16_t renegotiation_info_type;
+                       uint16_t renegotiation_info_len;
+                       struct {
+                               uint8_t len;
+                               uint8_t data[ tls->secure_renegotiation ?
+                                             sizeof ( tls->verify.client ) :0];
+                       } __attribute__ (( packed )) renegotiation_info;
                } __attribute__ (( packed )) extensions;
        } __attribute__ (( packed )) hello;
        struct tls_cipher_suite *suite;
@@ -995,6 +1034,14 @@ static int tls_send_client_hello ( struct tls_session *tls ) {
                = htons ( sizeof ( hello.extensions.signature_algorithms.code));
        i = 0 ; for_each_table_entry ( sighash, TLS_SIG_HASH_ALGORITHMS )
                hello.extensions.signature_algorithms.code[i++] = sighash->code;
+       hello.extensions.renegotiation_info_type
+               = htons ( TLS_RENEGOTIATION_INFO );
+       hello.extensions.renegotiation_info_len
+               = htons ( sizeof ( hello.extensions.renegotiation_info ) );
+       hello.extensions.renegotiation_info.len
+               = sizeof ( hello.extensions.renegotiation_info.data );
+       memcpy ( hello.extensions.renegotiation_info.data, tls->verify.client,
+                sizeof ( hello.extensions.renegotiation_info.data ) );
 
        return tls_send_handshake ( tls, &hello, sizeof ( hello ) );
 }
@@ -1201,20 +1248,24 @@ static int tls_send_finished ( struct tls_session *tls ) {
        struct digest_algorithm *digest = tls->handshake_digest;
        struct {
                uint32_t type_length;
-               uint8_t verify_data[12];
+               uint8_t verify_data[ sizeof ( tls->verify.client ) ];
        } __attribute__ (( packed )) finished;
        uint8_t digest_out[ digest->digestsize ];
        int rc;
 
+       /* Construct client verification data */
+       tls_verify_handshake ( tls, digest_out );
+       tls_prf_label ( tls, &tls->master_secret, sizeof ( tls->master_secret ),
+                       tls->verify.client, sizeof ( tls->verify.client ),
+                       "client finished", digest_out, sizeof ( digest_out ) );
+
        /* Construct record */
        memset ( &finished, 0, sizeof ( finished ) );
        finished.type_length = ( cpu_to_le32 ( TLS_FINISHED ) |
                                 htonl ( sizeof ( finished ) -
                                         sizeof ( finished.type_length ) ) );
-       tls_verify_handshake ( tls, digest_out );
-       tls_prf_label ( tls, &tls->master_secret, sizeof ( tls->master_secret ),
-                       finished.verify_data, sizeof ( finished.verify_data ),
-                       "client finished", digest_out, sizeof ( digest_out ) );
+       memcpy ( finished.verify_data, tls->verify.client,
+                sizeof ( finished.verify_data ) );
 
        /* Transmit record */
        if ( ( rc = tls_send_handshake ( tls, &finished,
@@ -1295,6 +1346,37 @@ static int tls_new_alert ( struct tls_session *tls, const void *data,
        }
 }
 
+/**
+ * Receive new Hello Request handshake record
+ *
+ * @v tls              TLS session
+ * @v data             Plaintext handshake record
+ * @v len              Length of plaintext handshake record
+ * @ret rc             Return status code
+ */
+static int tls_new_hello_request ( struct tls_session *tls,
+                                  const void *data __unused,
+                                  size_t len __unused ) {
+
+       /* Ignore if a handshake is in progress */
+       if ( ! tls_ready ( tls ) ) {
+               DBGC ( tls, "TLS %p ignoring Hello Request\n", tls );
+               return 0;
+       }
+
+       /* Fail unless server supports secure renegotiation */
+       if ( ! tls->secure_renegotiation ) {
+               DBGC ( tls, "TLS %p refusing to renegotiate insecurely\n",
+                      tls );
+               return -EPERM_RENEG_INSECURE;
+       }
+
+       /* Restart negotiation */
+       tls_restart ( tls );
+
+       return 0;
+}
+
 /**
  * Receive new Server Hello handshake record
  *
@@ -1317,7 +1399,23 @@ static int tls_new_server_hello ( struct tls_session *tls,
                uint8_t compression_method;
                char next[0];
        } __attribute__ (( packed )) *hello_b;
+       const struct {
+               uint16_t len;
+               uint8_t data[0];
+       } __attribute__ (( packed )) *exts;
+       const struct {
+               uint16_t type;
+               uint16_t len;
+               uint8_t data[0];
+       } __attribute__ (( packed )) *ext;
+       const struct {
+               uint8_t len;
+               uint8_t data[0];
+       } __attribute__ (( packed )) *reneg = NULL;
        uint16_t version;
+       size_t exts_len;
+       size_t ext_len;
+       size_t remaining;
        int rc;
 
        /* Parse header */
@@ -1332,6 +1430,56 @@ static int tls_new_server_hello ( struct tls_session *tls,
        session_id = hello_a->session_id;
        hello_b = ( ( void * ) ( session_id + hello_a->session_id_len ) );
 
+       /* Parse extensions, if present */
+       remaining = ( len - sizeof ( *hello_a ) - hello_a->session_id_len -
+                     sizeof ( *hello_b ) );
+       if ( remaining ) {
+
+               /* Parse extensions length */
+               exts = ( ( void * ) hello_b->next );
+               if ( ( sizeof ( *exts ) > remaining ) ||
+                    ( ( exts_len = ntohs ( exts->len ) ) >
+                      ( remaining - sizeof ( *exts ) ) ) ) {
+                       DBGC ( tls, "TLS %p received underlength extensions\n",
+                              tls );
+                       DBGC_HD ( tls, data, len );
+                       return -EINVAL_HELLO;
+               }
+
+               /* Parse extensions */
+               for ( ext = ( ( void * ) exts->data ), remaining = exts_len ;
+                     remaining ;
+                     ext = ( ( ( void * ) ext ) + sizeof ( *ext ) + ext_len ),
+                             remaining -= ( sizeof ( *ext ) + ext_len ) ) {
+
+                       /* Parse extension length */
+                       if ( ( sizeof ( *ext ) > remaining ) ||
+                            ( ( ext_len = ntohs ( ext->len ) ) >
+                              ( remaining - sizeof ( *ext ) ) ) ) {
+                               DBGC ( tls, "TLS %p received underlength "
+                                      "extension\n", tls );
+                               DBGC_HD ( tls, data, len );
+                               return -EINVAL_HELLO;
+                       }
+
+                       /* Record known extensions */
+                       switch ( ext->type ) {
+                       case htons ( TLS_RENEGOTIATION_INFO ) :
+                               reneg = ( ( void * ) ext->data );
+                               if ( ( sizeof ( *reneg ) > ext_len ) ||
+                                    ( reneg->len >
+                                      ( ext_len - sizeof ( *reneg ) ) ) ) {
+                                       DBGC ( tls, "TLS %p received "
+                                              "underlength renegotiation "
+                                              "info\n", tls );
+                                       DBGC_HD ( tls, data, len );
+                                       return -EINVAL_HELLO;
+                               }
+                               break;
+                       }
+               }
+       }
+
        /* Check and store protocol version */
        version = ntohs ( hello_a->version );
        if ( version < TLS_VERSION_TLS_1_0 ) {
@@ -1370,6 +1518,30 @@ static int tls_new_server_hello ( struct tls_session *tls,
        if ( ( rc = tls_generate_keys ( tls ) ) != 0 )
                return rc;
 
+       /* Handle secure renegotiation */
+       if ( tls->secure_renegotiation ) {
+
+               /* Secure renegotiation is expected; verify data */
+               if ( ( reneg == NULL ) ||
+                    ( reneg->len != sizeof ( tls->verify ) ) ||
+                    ( memcmp ( reneg->data, &tls->verify,
+                               sizeof ( tls->verify ) ) != 0 ) ) {
+                       DBGC ( tls, "TLS %p server failed secure "
+                              "renegotiation\n", tls );
+                       return -EPERM_RENEG_VERIFY;
+               }
+
+       } else if ( reneg != NULL ) {
+
+               /* Secure renegotiation is being enabled */
+               if ( reneg->len != 0 ) {
+                       DBGC ( tls, "TLS %p server provided non-empty initial "
+                              "renegotiation\n", tls );
+                       return -EPERM_RENEG_VERIFY;
+               }
+               tls->secure_renegotiation = 1;
+       }
+
        return 0;
 }
 
@@ -1569,11 +1741,10 @@ static int tls_new_finished ( struct tls_session *tls,
                              const void *data, size_t len ) {
        struct digest_algorithm *digest = tls->handshake_digest;
        const struct {
-               uint8_t verify_data[12];
+               uint8_t verify_data[ sizeof ( tls->verify.server ) ];
                char next[0];
        } __attribute__ (( packed )) *finished = data;
        uint8_t digest_out[ digest->digestsize ];
-       uint8_t verify_data[ sizeof ( finished->verify_data ) ];
 
        /* Sanity check */
        if ( sizeof ( *finished ) != len ) {
@@ -1585,10 +1756,10 @@ static int tls_new_finished ( struct tls_session *tls,
        /* Verify data */
        tls_verify_handshake ( tls, digest_out );
        tls_prf_label ( tls, &tls->master_secret, sizeof ( tls->master_secret ),
-                       verify_data, sizeof ( verify_data ), "server finished",
-                       digest_out, sizeof ( digest_out ) );
-       if ( memcmp ( verify_data, finished->verify_data,
-                     sizeof ( verify_data ) ) != 0 ) {
+                       tls->verify.server, sizeof ( tls->verify.server ),
+                       "server finished", digest_out, sizeof ( digest_out ) );
+       if ( memcmp ( tls->verify.server, finished->verify_data,
+                     sizeof ( tls->verify.server ) ) != 0 ) {
                DBGC ( tls, "TLS %p verification failed\n", tls );
                return -EPERM_VERIFY;
        }
@@ -1644,6 +1815,10 @@ static int tls_new_handshake ( struct tls_session *tls,
 
                /* Handle payload */
                switch ( handshake->type ) {
+               case TLS_HELLO_REQUEST:
+                       rc = tls_new_hello_request ( tls, payload,
+                                                    payload_len );
+                       break;
                case TLS_SERVER_HELLO:
                        rc = tls_new_server_hello ( tls, payload, payload_len );
                        break;
@@ -2622,18 +2797,12 @@ int add_tls ( struct interface *xfer, const char *name,
                      ( sizeof ( tls->pre_master_secret.random ) ) ) ) != 0 ) {
                goto err_random;
        }
-       digest_init ( &md5_sha1_algorithm, tls->handshake_md5_sha1_ctx );
-       digest_init ( &sha256_algorithm, tls->handshake_sha256_ctx );
-       tls->handshake_digest = &sha256_algorithm;
-       tls->handshake_ctx = tls->handshake_sha256_ctx;
-       tls->tx_pending = TLS_TX_CLIENT_HELLO;
        iob_populate ( &tls->rx_header_iobuf, &tls->rx_header, 0,
                       sizeof ( tls->rx_header ) );
        INIT_LIST_HEAD ( &tls->rx_data );
 
-       /* Add pending operations for server and client Finished messages */
-       pending_get ( &tls->client_negotiation );
-       pending_get ( &tls->server_negotiation );
+       /* Start negotiation */
+       tls_restart ( tls );
 
        /* Attach to parent interface, mortalise self, and return */
        intf_plug_plug ( &tls->plainstream, xfer );