• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 13219613273

08 Feb 2025 08:36PM UTC coverage: 91.657% (+0.003%) from 91.654%
13219613273

push

github

web-flow
Merge pull request #4650 from randombit/jack/header-minimization

Reorganize code and reduce header dependencies

94838 of 103471 relevant lines covered (91.66%)

11212567.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.59
/src/lib/tls/tls_session_manager.cpp
1
/**
2
 * TLS Session Manger base class implementations
3
 * (C) 2011-2023 Jack Lloyd
4
 *     2022-2023 René Meusel - Rohde & Schwarz Cybersecurity
5
 *
6
 * Botan is released under the Simplified BSD License (see license.txt)
7
 */
8

9
#include <botan/tls_session_manager.h>
10

11
#include <botan/rng.h>
12
#include <botan/tls_callbacks.h>
13
#include <botan/tls_policy.h>
14
#include <algorithm>
15

16
namespace Botan::TLS {
17

18
Session_Manager::Session_Manager(const std::shared_ptr<RandomNumberGenerator>& rng) : m_rng(rng) {
9,629✔
19
   BOTAN_ASSERT_NONNULL(m_rng);
9,629✔
20
}
9,629✔
21

22
std::optional<Session_Handle> Session_Manager::establish(const Session& session,
238✔
23
                                                         const std::optional<Session_ID>& id,
24
                                                         bool tls12_no_ticket) {
25
   // Establishing a session does not require locking at this level as
26
   // concurrent TLS instances on a server will create unique sessions.
27

28
   // By default, the session manager does not emit session tickets anyway
29
   BOTAN_UNUSED(tls12_no_ticket);
238✔
30
   BOTAN_ASSERT(session.side() == Connection_Side::Server, "Client tried to establish a session");
238✔
31

32
   Session_Handle handle(id.value_or(m_rng->random_vec<Session_ID>(32)));
714✔
33
   store(session, handle);
238✔
34
   return handle;
238✔
35
}
238✔
36

37
std::optional<Session> Session_Manager::retrieve(const Session_Handle& handle,
581✔
38
                                                 Callbacks& callbacks,
39
                                                 const Policy& policy) {
40
   // Retrieving a session for a given handle does not require locking on this
41
   // level. Concurrent threads might handle the removal of an expired ticket
42
   // more than once, but removing an already removed ticket is a harmless NOOP.
43

44
   auto session = retrieve_one(handle);
581✔
45
   if(!session.has_value()) {
581✔
46
      return std::nullopt;
185✔
47
   }
48

49
   // A value of '0' means: No policy restrictions.
50
   const std::chrono::seconds policy_lifetime =
396✔
51
      (policy.session_ticket_lifetime().count() > 0) ? policy.session_ticket_lifetime() : std::chrono::seconds::max();
396✔
52

53
   // RFC 5077 3.3 -- "Old Session Tickets"
54
   //    A server MAY treat a ticket as valid for a shorter or longer period of
55
   //    time than what is stated in the ticket_lifetime_hint.
56
   //
57
   // RFC 5246 F.1.4 -- TLS 1.2
58
   //    If either party suspects that the session may have been compromised, or
59
   //    that certificates may have expired or been revoked, it should force a
60
   //    full handshake.  An upper limit of 24 hours is suggested for session ID
61
   //    lifetimes.
62
   //
63
   // RFC 8446 4.6.1 -- TLS 1.3
64
   //    A server MAY treat a ticket as valid for a shorter period of time than
65
   //    what is stated in the ticket_lifetime.
66
   //
67
   // Note: This disregards what is stored in the session (e.g. "lifetime_hint")
68
   //       and only takes the local policy into account. The lifetime stored in
69
   //       the sessions was taken from the same policy anyways and changes by
70
   //       the application should have an immediate effect.
71
   const auto ticket_age =
396✔
72
      std::chrono::duration_cast<std::chrono::seconds>(callbacks.tls_current_timestamp() - session->start_time());
396✔
73
   const bool expired = ticket_age > policy_lifetime;
396✔
74

75
   if(expired) {
396✔
76
      remove(handle);
10✔
77
      return std::nullopt;
10✔
78
   } else {
79
      return session;
967✔
80
   }
81
}
581✔
82

83
std::vector<Session_with_Handle> Session_Manager::find_and_filter(const Server_Information& info,
3,648✔
84
                                                                  Callbacks& callbacks,
85
                                                                  const Policy& policy) {
86
   // A value of '0' means: No policy restrictions. Session ticket lifetimes as
87
   // communicated by the server apply regardless.
88
   const std::chrono::seconds policy_lifetime =
3,648✔
89
      (policy.session_ticket_lifetime().count() > 0) ? policy.session_ticket_lifetime() : std::chrono::seconds::max();
3,648✔
90

91
   const size_t max_sessions_hint = std::max(policy.maximum_session_tickets_per_client_hello(), size_t(1));
3,648✔
92
   const auto now = callbacks.tls_current_timestamp();
3,648✔
93

94
   // An arbitrary number of loop iterations to perform before giving up
95
   // to avoid a potential endless loop with a misbehaving session manager.
96
   constexpr unsigned int max_attempts = 10;
3,648✔
97
   std::vector<Session_with_Handle> sessions_and_handles;
3,648✔
98

99
   // Query the session manager implementation for new sessions until at least
100
   // one session passes the filter or no more sessions are found.
101
   for(unsigned int attempt = 0; attempt < max_attempts && sessions_and_handles.empty(); ++attempt) {
4,068✔
102
      sessions_and_handles = find_some(info, max_sessions_hint);
3,657✔
103

104
      // ... underlying implementation didn't find anything. Early exit.
105
      if(sessions_and_handles.empty()) {
3,657✔
106
         break;
107
      }
108

109
      // TODO: C++20, use std::ranges::remove_if() once XCode and Android NDK caught up.
110
      sessions_and_handles.erase(
420✔
111
         std::remove_if(sessions_and_handles.begin(),
420✔
112
                        sessions_and_handles.end(),
113
                        [&](const auto& session) {
831✔
114
                           const auto age =
115
                              std::chrono::duration_cast<std::chrono::seconds>(now - session.session.start_time());
831✔
116

117
                           // RFC 5077 3.3 -- "Old Session Tickets"
118
                           //    The ticket_lifetime_hint field contains a hint from the
119
                           //    server about how long the ticket should be stored. [...]
120
                           //    A client SHOULD delete the ticket and associated state when
121
                           //    the time expires. It MAY delete the ticket earlier based on
122
                           //    local policy.
123
                           //
124
                           // RFC 5246 F.1.4 -- TLS 1.2
125
                           //    If either party suspects that the session may have been
126
                           //    compromised, or that certificates may have expired or been
127
                           //    revoked, it should force a full handshake.  An upper limit of
128
                           //    24 hours is suggested for session ID lifetimes.
129
                           //
130
                           // RFC 8446 4.2.11.1 -- TLS 1.3
131
                           //    The client's view of the age of a ticket is the time since the
132
                           //    receipt of the NewSessionTicket message.  Clients MUST NOT
133
                           //    attempt to use tickets which have ages greater than the
134
                           //    "ticket_lifetime" value which was provided with the ticket.
135
                           //
136
                           // RFC 8446 4.6.1 -- TLS 1.3
137
                           //    Clients MUST NOT cache tickets for longer than 7 days,
138
                           //    regardless of the ticket_lifetime, and MAY delete tickets
139
                           //    earlier based on local policy.
140
                           //
141
                           // Note: TLS 1.3 tickets with a lifetime longer than 7 days are
142
                           //       rejected during parsing with an "Illegal Parameter" alert.
143
                           //       Other suggestions are left to the application via
144
                           //       Policy::session_ticket_lifetime(). Session lifetimes as
145
                           //       communicated by the server via the "lifetime_hint" are
146
                           //       obeyed regardless of the policy setting.
147
                           const auto session_lifetime_hint = session.session.lifetime_hint();
831✔
148
                           const bool expired = age > std::min(policy_lifetime, session_lifetime_hint);
1,688✔
149

150
                           if(expired) {
831✔
151
                              remove(session.handle);
22✔
152
                           }
153

154
                           return expired;
831✔
155
                        }),
156
         sessions_and_handles.end());
420✔
157
   }
158

159
   return sessions_and_handles;
3,648✔
160
}
×
161

162
std::vector<Session_with_Handle> Session_Manager::find(const Server_Information& info,
3,648✔
163
                                                       Callbacks& callbacks,
164
                                                       const Policy& policy) {
165
   auto allow_reusing_tickets = policy.reuse_session_tickets();
3,648✔
166

167
   // Session_Manager::find() must be an atomic getter if ticket reuse is not
168
   // allowed. I.e. each ticket handed to concurrently requesting threads must
169
   // be unique. In that case we must hold a lock while retrieving a ticket.
170
   // Otherwise, no locking is required on this level.
171
   std::optional<lock_guard_type<recursive_mutex_type>> lk;
3,648✔
172
   if(!allow_reusing_tickets) {
3,648✔
173
      lk.emplace(mutex());
3,625✔
174
   }
175

176
   auto sessions_and_handles = find_and_filter(info, callbacks, policy);
3,648✔
177

178
   // std::vector::resize() cannot be used as the vector's members aren't
179
   // default constructible.
180
   const auto session_limit = policy.maximum_session_tickets_per_client_hello();
3,648✔
181
   while(session_limit > 0 && sessions_and_handles.size() > session_limit) {
4,023✔
182
      sessions_and_handles.pop_back();
375✔
183
   }
184

185
   // RFC 8446 Appendix C.4
186
   //    Clients SHOULD NOT reuse a ticket for multiple connections. Reuse of
187
   //    a ticket allows passive observers to correlate different connections.
188
   //
189
   // When reuse of session tickets is not allowed, remove all tickets to be
190
   // returned from the implementation's internal storage.
191
   if(!allow_reusing_tickets) {
3,648✔
192
      // The lock must be held here, otherwise we cannot guarantee the
193
      // transactional retrieval of tickets to concurrently requesting clients.
194
      BOTAN_ASSERT_NOMSG(lk.has_value());
3,625✔
195
      for(const auto& [session, handle] : sessions_and_handles) {
4,023✔
196
         if(!session.version().is_pre_tls_13() || !handle.is_id()) {
398✔
197
            remove(handle);
265✔
198
         }
199
      }
200
   }
201

202
   return sessions_and_handles;
3,648✔
203
}
3,648✔
204

205
#if defined(BOTAN_HAS_TLS_13)
206

207
std::optional<std::pair<Session, uint16_t>> Session_Manager::choose_from_offered_tickets(
112✔
208
   const std::vector<PskIdentity>& tickets,
209
   std::string_view hash_function,
210
   Callbacks& callbacks,
211
   const Policy& policy) {
212
   // Note that the TLS server currently does not ensure that tickets aren't
213
   // reused. As a result, no locking is required on this level.
214

215
   for(uint16_t i = 0; const auto& ticket : tickets) {
132✔
216
      auto session = retrieve(Opaque_Session_Handle(ticket.identity()), callbacks, policy);
246✔
217
      if(session.has_value() && session->ciphersuite().prf_algo() == hash_function &&
457✔
218
         session->version().is_tls_13_or_later()) {
228✔
219
         return std::pair{std::move(session.value()), i};
103✔
220
      }
221

222
      // RFC 8446 4.2.10
223
      //    For PSKs provisioned via NewSessionTicket, a server MUST validate
224
      //    that the ticket age for the selected PSK identity [...] is within a
225
      //    small tolerance of the time since the ticket was issued.  If it is
226
      //    not, the server SHOULD proceed with the handshake but reject 0-RTT,
227
      //    and SHOULD NOT take any other action that assumes that this
228
      //    ClientHello is fresh.
229
      //
230
      // TODO: The ticket-age is currently not checked (as 0-RTT is not
231
      //       implemented) and we simply take the SHOULD at face value.
232
      //       Instead we could add a policy check letting the user decide.
233

234
      ++i;
20✔
235
   }
123✔
236

237
   return std::nullopt;
9✔
238
}
239

240
#endif
241

242
}  // namespace Botan::TLS
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc