diff --git a/lib/modules/SSL.pmod/ClientConnection.pike b/lib/modules/SSL.pmod/ClientConnection.pike index f9d5762f6d4d68ba3518d947740636d8b8dcb470..49c1aacb59d165aabafbe1789406d5ba9d53eac8 100644 --- a/lib/modules/SSL.pmod/ClientConnection.pike +++ b/lib/modules/SSL.pmod/ClientConnection.pike @@ -186,6 +186,21 @@ protected Packet client_hello(string(8bit)|void server_name, return Buffer()->add_hstring(hostname, 2); }; + ext (EXTENSION_session_ticket, 1) + { + // RFC 4507 and RFC 5077. + if (session->ticket_expiry_time < time(1)) { + session->ticket = UNDEFINED; + session->ticket_expiry_time = 0; + } + SSL3_DEBUG_MSG("SSL.ClientConnection: Sending ticket %O.\n", + session->ticket); + // NB: RFC 4507 and RFC 5077 differ in encoding here. + // Apparently no implementations actually followed + // the RFC 4507 encoding. + return Buffer()->add(session->ticket || ""); + }; + ext (EXTENSION_application_layer_protocol_negotiation, !!(context->advertised_protocols)) { @@ -585,6 +600,32 @@ protected int(-1..0) got_certificate_request(Buffer input) return 0; } +protected int(-1..1) got_new_session_ticket(Buffer input) +{ + COND_FATAL(!tickets_enabled, ALERT_handshake_failure, + "Unexpected session ticket.\n"); + // Make sure that we only get one ticket. + tickets_enabled = 3; + + int lifetime_hint = input->read_int(4); + string(8bit) ticket = input->read_hstring(2); + + SSL3_DEBUG_MSG("SSL.ClientConnection: Got ticket %O (%d seconds).\n", + ticket, lifetime_hint); + + COND_FATAL(!sizeof(ticket), ALERT_handshake_failure, + "Empty ticket.\n"); + + if (!lifetime_hint) { + // Unspecified lifetime. Handle as one hour. + lifetime_hint = 3600; + } + + session->ticket = ticket; + session->ticket_expiry_time = lifetime_hint + time(1); + return 0; +} + //! Do handshake processing. Type is one of HANDSHAKE_*, data is the //! contents of the packet, and raw is the raw packet received (needed //! for supporting SSLv2 hello messages). @@ -604,11 +645,13 @@ int(-1..1) handle_handshake(int type, Buffer input, Stdio.Buffer raw) werror("sizeof(data)="+sizeof(data)+"\n"); #endif +#if 0 // Not compatible with session tickets... // Enforce packet ordering. COND_FATAL(type <= previous_handshake, ALERT_unexpected_message, "Invalid handshake packet order.\n"); previous_handshake = type; +#endif switch(handshake_state) { @@ -809,6 +852,13 @@ int(-1..1) handle_handshake(int type, Buffer input, Stdio.Buffer raw) application_protocol = selected_prot[0]; break; + case EXTENSION_session_ticket: + COND_FATAL(sizeof(extension_data), ALERT_handshake_failure, + "Invalid server session ticket extension.\n"); + SSL3_DEBUG_MSG("SSL.ClientConnection: Server supports tickets.\n"); + tickets_enabled = 1; + break; + case EXTENSION_heartbeat: { int hb_mode; @@ -878,6 +928,13 @@ int(-1..1) handle_handshake(int type, Buffer input, Stdio.Buffer raw) } } + if (session->ticket && !tickets_enabled) { + // The server has stopped supporting session tickets? + // Make sure not to compare the server-generated + // session id with the one that we may have generated. + session_id = ""; + } + // RFC 5746 3.5: // When a ServerHello is received, the client MUST verify that the // "renegotiation_info" extension is present; if it is not, the @@ -897,11 +954,29 @@ int(-1..1) handle_handshake(int type, Buffer input, Stdio.Buffer raw) handle_change_cipher(1); } - handshake_state = STATE_wait_for_finish; + if (tickets_enabled) { + handshake_state = STATE_wait_for_ticket; + // Expect the ticket before the CC. + expect_change_cipher--; + } else { + handshake_state = STATE_wait_for_finish; + } reuse = 1; break; } + if ((session_id == "") && tickets_enabled) { + // Generate a session identifier. + // NB: We currently do NOT support resumption based on an + // empty session id and a HANDSHAKE_new_session_ticket. + // We thus need a non-empty session id when we use + // this new session for resumption. We don't care much + // about the value (as long as it is non-empty), as + // a compliant server will return either the value we + // provided, or the empty string. + session_id = "RESUMPTION_TICKET"; + } + session->identity = session_id; if (version >= PROTOCOL_TLS_1_3) { handshake_state = STATE_wait_for_key_share; @@ -1008,7 +1083,14 @@ int(-1..1) handle_handshake(int type, Buffer input, Stdio.Buffer raw) "Unexpected server message.\n"); if (send_certs()) return -1; - handshake_state = STATE_wait_for_finish; + + if (tickets_enabled) { + handshake_state = STATE_wait_for_ticket; + // Expect the ticket before the CC. + expect_change_cipher--; + } else { + handshake_state = STATE_wait_for_finish; + } break; } break; @@ -1045,6 +1127,21 @@ int(-1..1) handle_handshake(int type, Buffer input, Stdio.Buffer raw) } break; + case STATE_wait_for_ticket: + { + COND_FATAL(type != HANDSHAKE_new_session_ticket, ALERT_unexpected_message, + "Expected new session ticket.\n"); + + SSL3_DEBUG_MSG("SSL.ClientConnection: NEW_SESSION_TICKET\n"); + add_handshake_message(raw); + + // Expect CC. + expect_change_cipher++; + handshake_state = STATE_wait_for_finish; + return got_new_session_ticket(input); + } + break; + case STATE_wait_for_finish: { COND_FATAL(type != HANDSHAKE_finished, ALERT_unexpected_message, diff --git a/lib/modules/SSL.pmod/Constants.pmod b/lib/modules/SSL.pmod/Constants.pmod index 2d336b78a75a16acce945c7e4007502ca860cccc..56a864e81cde574d1955a57f8489be902a697c62 100644 --- a/lib/modules/SSL.pmod/Constants.pmod +++ b/lib/modules/SSL.pmod/Constants.pmod @@ -45,8 +45,9 @@ constant STATE_wait_for_hello = 0; constant STATE_wait_for_key_share = 1; constant STATE_wait_for_peer = 2; constant STATE_wait_for_verify = 3; -constant STATE_wait_for_finish = 4; -constant STATE_handshake_finished = 5; +constant STATE_wait_for_ticket = 4; +constant STATE_wait_for_finish = 5; +constant STATE_handshake_finished = 6; //! Connection states. //!