Skip to content

[TLS 1.3] Overhaul TLS::Callbacks#2988

Merged
reneme merged 7 commits intomasterfrom
tls13/callbacks
Dec 15, 2022
Merged

[TLS 1.3] Overhaul TLS::Callbacks#2988
reneme merged 7 commits intomasterfrom
tls13/callbacks

Conversation

@reneme
Copy link
Copy Markdown
Collaborator

@reneme reneme commented Jun 7, 2022

Status

This is meant to collect changes to the TLS callbacks (i.e. user-facing API changes). Currently, it is based on master where all client-side TLS 1.3 feature branches are merged. Changes required for the server implementation are additionally cherry-picked here. Note that this builds on the ideas outlined here.

Callbacks that need attention

Here is a collection of insights we had during implementation with suggestions how to tackle the new requirements TLS 1.3 brings to the implementations. Those changes are not yet implemented and might require some discussion.

A word on 0-RTT

We don't have immediate plans to implement it. However, it would certainly have user-facing API implications. I have some hope to be able restrict the changes to additional API, rather than breaking changes, though. I.e. deferring 0-RTT support to after Botan 3.0 is released seems feasible to me.

tls_session_established(const Session&) (mandatory callback)

Currently, applications are required to override that. The TLS 1.2 implementation calls it once a session is established, provides the established session's descriptor and applications must decide whether to persist the session for resumption or discard it. TLS 1.3 got rid of this type of session resumption. Instead it exclusively uses session tickets and allows multiple such tickets per connection (see Callbacks::tls_session_ticket_received).

I don't see a reasonable way to serve this callback with TLS 1.3. @randombit

Ideas:

  • replace tls_session_established using a dedicated "session descriptor"
    • deprecate the old callback and keep serving it from TLS 1.2 for now
    • use tls_session_ticket_received for TLS 1.2 session resumption storage decision
      • might mandate a different name (tls_session_should_be_stored ?)
    • new tls_session_established
      • pass a TLS::Summary object, containing info about the new session, e.g
        • protocol version
        • negotiated ciphersuite
        • extended master secret?
        • encrypt-then-MAC?
        • ...
      • would be purely "informational" (callback returns void)
      • might obsolete tls_session_activated() as it carries roughly the same info without the TLS::Summary
  • rename tls_session_established() to tls_12_session_established()
    • probably shouldn't be mandatory
      • default implementation always returns true, I guess
    • probably also needs a way to get a connection summary (TLS::Summary as described above)

Side-Note: New TLS::Server API to send new tickets

TLS 1.3 allows for an arbitrary number of tickets at any time after the handshake not just exactly one at the end of the handshake. Hence, we'll need something like TLS::Server::send_new_session_ticket(std::chrono::seconds lifetime) allowing applications to send tickets at their own discretion.

Side-Note: Application-defined Session Ticket content

RFC 8446 4.6.1: "The ticket itself is an opaque label. It MAY be either a database lookup key or a self-encrypted and self-authenticated value." And I think we should allow the application to decide that.

Given the current Session_Manager API, we might extend the sematics of ::save(const Session&) to return the "ticket" where applications could return a lookup handle or the result of Session::encrypt(). Session_Manager::load_from_session_id() already has the right function signature to act as a counter-part. Nevertheless, I'd suggest to rename it to something like restore_from_ticket() for clarity.

tls_dh_agree() / tls_ecdh_agree() along with tls_decode_group_param()

The agreement callbacks receive a public value along with a group specification. They generate a new keypair, perform an agreement and return a tuple of the shared secret and the public value to pass to the peer.

TLS 1.3 encourages clients to offer initial key share values. That allows the server to encrypt most of its first flight. In contrast to TLS 1.2 where the initial key offer is provided by the server in its first flight.

Technically, we could use the above-mentioned callbacks for TLS 1.3 as well. Main difference: The tls_xx_agree() callbacks would be called in clients in a TLS 1.2 session establishment and in servers in a TLS 1.3 session establishment. That might cause confusion.
Additionally, the callbacks are currently not used for the key agreement in the opposite direction. Instead the PK_Key_Agreement is hard-coded.

With KEMs entering the game for PQC-ready TLS we would likely want to add a tls_kem_agree() if we decide to keep those callbacks at all.

If we decide to keep these callbacks, I would suggest to separate ephemeral key generation from the actual agreement. Then both client and server as well as TLS 1.2 and 1.3 could make use of the same callback. To really be useful, one would probably also want to extract the key generation into a callback. Also, we might even abstract from the DH, ECDH (and KEM) differentiation and just have two callbacks, like so:

struct {
  std::vector<uint8_t> public_value;
  std::unique_ptr<Private_Key> private_key;
} tls_generate_ephemeral_key(Group_Params params,
                             bool compressed_public_value,
                             RandomNumberGenerator& rng, ...);

secure_vector<uint8_t>
tls_key_agreement(Group_Params         params,
                  std::vector<uint8_t> peer_public_value,
                  Private_Key& local_private_key, 
                  ...);

Does that sound about right? Somehow, I feel I'm missing the gist of that interface. @randombit

tls_log_error(), ...debug() and debug_bin()

Not actually related to TLS 1.3; though none of these callbacks are used. Now might be a good time to remove them. YAGNI. ;)

Modifications at a Glance

Those modifications are already implemented and merged or are proposed as working implementations in this pull request.

  • tls_modify_extensions
    Now has an additional parameter (which_message) that provides context on what handshake message contains the extensions to be modified. The new overload is called for extensible messages introduced in TLS 1.3. The old overload is marked deprecated and is called by the default implementation of the new overload (after filtering out new TLS 1.3 extensible messages to keep its behaviour as close to legacy as possible).

  • tls_examine_extensions
    Analogous to the modify callback above.

  • tls_session_ticket_received
    Added for TLS 1.3 session tickets. Not touched in this pull request.

  • tls_parse_ocsp_response
    Added to influence the OCSP parsing behaviour. Mainly used to better support BoGo tests which send invalid responses that need to be ignored. Not touched in this pull request.

  • tls_current_timestamp
    Added to inject user-defined timestamps. Mainly used for deterministic testing (using test vectors from RFC 8448) and certain BoGo tests that define a specific waiting period. Not touched in this pull request.

  • tls_peer_closed_connection
    In contrast to older revisions, TLS 1.3 does not mandate an immediate in-kind response when receiving "close_notify". Applications can override this callback to get notified that no more data is to be expected from the peer. With TLS 1.3 they might legally continue sending data and finish with Channel::close() at their own discretion. Returning true from this callback will make TLS 1.3 behave just like 1.2 and reply with a "close_notify" immediately. This is the default behaviour.

  • tls_provide_cert_chain_status
    This allows applications to provide stapled OCSP responses for all (or some) intermediate certificates added to the Certificate handshake message. To the best of my understanding, previous versions of TLS allowed stapled OCSP responses for the leaf certificate, only.
    By default, this callback invokes the old tls_provide_cert_status() callback: mimicking the behavior of TLS 1.2 and stapling an OCSP response for the leaf certificate, only.

@reneme reneme added this to the Botan 3.0.0 milestone Sep 20, 2022
@reneme reneme changed the base branch from tls13/integration to master October 20, 2022 06:20
@reneme reneme force-pushed the tls13/callbacks branch 2 times, most recently from 83d36e5 to 8d8f518 Compare November 18, 2022 11:08
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Nov 18, 2022

Codecov Report

Base: 91.80% // Head: 91.80% // Decreases project coverage by -0.00% ⚠️

Coverage data is based on head (f5d7228) compared to base (b202720).
Patch coverage: 94.23% of modified lines in pull request are covered.

❗ Current head f5d7228 differs from pull request most recent head a12155e. Consider uploading reports for the commit a12155e to get more accurate results

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2988      +/-   ##
==========================================
- Coverage   91.80%   91.80%   -0.01%     
==========================================
  Files         599      599              
  Lines       72086    72138      +52     
  Branches     6601     6609       +8     
==========================================
+ Hits        66181    66225      +44     
- Misses       5905     5913       +8     
Impacted Files Coverage Δ
src/lib/tls/tls_callbacks.cpp 80.00% <75.00%> (-1.40%) ⬇️
src/lib/tls/msg_client_hello.cpp 91.79% <100.00%> (ø)
src/lib/tls/msg_server_hello.cpp 93.20% <100.00%> (ø)
src/lib/tls/tls12/tls_channel_impl_12.cpp 89.75% <100.00%> (+0.02%) ⬆️
src/lib/tls/tls12/tls_client_impl_12.cpp 90.88% <100.00%> (ø)
src/lib/tls/tls12/tls_server_impl_12.cpp 88.18% <100.00%> (ø)
src/lib/tls/tls13/msg_certificate_13.cpp 92.22% <100.00%> (+0.36%) ⬆️
src/lib/tls/tls13/tls_channel_impl_13.cpp 95.87% <100.00%> (+0.04%) ⬆️
src/lib/tls/tls13/tls_client_impl_13.cpp 95.37% <100.00%> (+0.05%) ⬆️
src/tests/test_tls_rfc8448.cpp 89.89% <100.00%> (+0.39%) ⬆️
... and 5 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

reneme and others added 6 commits December 9, 2022 14:23
This provides a hook for the application to decide whether
a received close_notify from the peer should be echoed or
not.
... that allows applications to provide stapled OCSP responses
for all (or some) certificates added to the Certificate hand-
shake message.
@reneme reneme requested a review from randombit December 9, 2022 14:27
Copy link
Copy Markdown
Owner

@randombit randombit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change itself looks fine. My only real comment would be - should we really bother maintaining the deprecated 2-argument versions of the extensions manipulation functions? These are not used by typical applications anyway, and it would be easier to just update the signatures and mention the change in the migration guide.

@reneme
Copy link
Copy Markdown
Collaborator Author

reneme commented Dec 14, 2022

Fully Agree on the extension manipulations. I'd be interested in your take on the dh/ecdh_agree() callbacks and the tls_session_established() callbacks, though. Currently, I didn't change the code for those but just expressed my thoughts about them in the PR description.

@randombit
Copy link
Copy Markdown
Owner

The DH/ECDH callbacks where IIRC added at the request of @securitykernel in order to handle a few uses cases RSCS had namely around using custom (non-standard) ECC curves and also supporting hardware acceleration of the operations. We could IMO remove those entirely if you don't have a need along these lines anymore. It would simplify things a bit, especially if we did not have to account for any non-standard curves. For HW operations it might be better to approach this in some other way entirely, since as it is it only works in the context of TLS and not for general operations.

@randombit
Copy link
Copy Markdown
Owner

Alternately your proposal with tls_generate_ephemeral_key looks good, it would in any case simplify the current mechanism. I certainly don't think we need to retain full compatability here in the callbacks; almost all applications likely use the default version.

@randombit
Copy link
Copy Markdown
Owner

On session resumption, a strawman proposal:

// called for both TLS 1.2 and 1.3. Optional callback, is informative
void tls_session_established(const TLS::Summary& session_info);
// Called only for TLS 1.2. Also optional, returns true by default
bool tls_12_should_persist_session(const TLS::Session& session);

IIRC tls_session_activated is called slightly later, at the point that it is possible to write to the channel successfully. So we may need to retain this.

@reneme reneme marked this pull request as ready for review December 15, 2022 07:16
@reneme
Copy link
Copy Markdown
Collaborator Author

reneme commented Dec 15, 2022

Thanks for your comments.

Re: Session resumption: That's roughly what I had in mind 👍. I'll introduce those changes in a separate PR dealing with server-side session resumption (hopefully before the end of the year).

Re: KEX callbacks: A quick glance into the consuming code didn't seem to use those. But I could certainly miss other internal products that might use it. @securitykernel ?

I'll merge this now but might come back to this discussion.

@reneme reneme merged commit 08c54f5 into master Dec 15, 2022
@randombit randombit deleted the tls13/callbacks branch December 18, 2022 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants