Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support RFC 5077 TLS session ticket reuse #166

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

cyang1
Copy link

@cyang1 cyang1 commented May 8, 2020

Adds an option to enable RFC 5077 TLS session ticket reuse, and implementations for all backends.

  • The OpenSSL implementation uses set_session_cache_mode, set_new_session_callback, set_remove_session_callback, and set_session to store just the previous session that was negotiated. It doesn't look like there's a way to store any more than that, as OpenSSL doesn't pass any information about domain/host to the callbacks.
  • The Schannel implementation implements a small (10 entry) LRU cache of SchannelCred, with an expiration of 10 minutes.
  • The Security Framework implementation just calls a system API, see implementation and tests in Support TLS session ticket resumption kornelski/rust-security-framework#95

Also added implementation-specific unit tests for OpenSSL and Schannel. A platform-agnostic one isn't really possible because macOS doesn't expose any way of determining of the stream was successfully resumed, and we're not at a low enough abstraction level to look at the handshake process itself.

@sfackler
Copy link
Owner

sfackler commented May 8, 2020

See https://github.com/sfackler/hyper-openssl/blob/master/src/lib.rs for how to have a more robust cache with OpenSSL.

@sfackler
Copy link
Owner

sfackler commented May 8, 2020

Using the domain provided for hostname verification seems like it's not a fully accurate identifier - it doesn't account for multiple servers running on the same host, for example.

@cyang1
Copy link
Author

cyang1 commented May 8, 2020

RFC 5077 presents an approach to stateless resumption though, so it should be possible to resume a TLS session initiated on a different server as long as both machines have access to the same session key used to encrypt the session.

Thanks for the pointer for OpenSSL. I'll take a look and make some changes.

@cyang1
Copy link
Author

cyang1 commented May 8, 2020

Not sure what's going on here with the Windows CI, it works for me locally on Windows 10. Maybe the OS version is too old? According to the Appveyor docs on the build environment,

If the build configuration does not specify build worker image then Visual Studio 2015 image is used.

Which should correspond to Windows Server 2012 R2.

Microsoft docs indicate that RFC 5077 was implemented starting in Windows 8, which should include Windows Server 2012 R2 as well.

@sfackler
Copy link
Owner

sfackler commented May 8, 2020

I am talking about the standard case where the servers would not share a cache.

@cyang1
Copy link
Author

cyang1 commented May 8, 2020

RFC 5077 only requires that servers share a secret that they can all use to encrypt/decrypt the tickets. For example, Cloudflare wrote up how they do this in a portion of this tangentially related blog post.

Arguably, implementations of RFC 5077 that do not share a secret across servers are incorrect, as they would not satisfy the usecases described in that RFC:

This mechanism is useful in the following situations:

  1. servers that handle a large number of transactions from different
    users
  2. servers that desire to cache sessions for a long time
  3. ability to load balance requests across servers
  4. embedded servers with little memory

@sfackler
Copy link
Owner

sfackler commented May 8, 2020

Yes, I am aware that is is possible for servers to share cached sessions. I am saying that if foobar.com:443 and foobar.com:1234 do not, in fact, share cached sessions, this implementation will cause their sessions to collide.

@cyang1
Copy link
Author

cyang1 commented May 8, 2020

I agree that that is a possible failure case, but it seems relatively unlikely in standard usage, and IMO, the failure mode would be rather harmless:

  • From a secrecy perspective, the server would be presented with and reject a nonsensical session ticket, nothing is leaked since the ticket is encrypted.
  • From a user experience perspective, it would just silently fall back to a full TLS handshake.

Given that this is a bit of an edge case and the failure mode is reasonable, I think it should be fine to leave the behavior as is. Open to suggestions on how to resolve it as well, but AFAIK we don't have the relevant information at this abstraction level, since port is only really relevant at TCP connection time.

@cyang1 cyang1 force-pushed the rfc5077 branch 2 times, most recently from 8245c59 to 1fd8a7c Compare May 9, 2020 00:22
@cyang1
Copy link
Author

cyang1 commented May 9, 2020

Updated the OpenSSL implementation (mostly just inlining the logic from hyper-openssl).

Also pinned the Appveyor image used to Visual Studio 2017, which should use Windows Server 2016. Looks like that still failed though. In hindsight, OS version probably wasn't the issue since only one of the tests was failing (connect_session_ticket_resumption_two_sites), and the basic test passed (connect_session_ticket_resumption).

Not sure why it's failing still. I'll try to run the tests on Windows 8.1 locally and see if that can repro.

EDIT: Tests also pass for me on Windows 8.1 locally. I don't have any Windows Server installations around to test there, but I don't know why it would produce different results.

@@ -7,6 +7,7 @@ use self::security_framework::base;
use self::security_framework::certificate::SecCertificate;
use self::security_framework::identity::SecIdentity;
use self::security_framework::import_export::{ImportedIdentity, Pkcs12ImportOptions};
use self::security_framework::os::macos::import_export::Pkcs12ImportOptionsExt;
use self::security_framework::secure_transport::{
self, ClientBuilder, SslConnectionType, SslContext, SslProtocol, SslProtocolSide,
};
Copy link
Owner

Choose a reason for hiding this comment

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

Doesn't set_peer_id need to be called here?

Copy link
Author

Choose a reason for hiding this comment

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

It's called internally in https://github.com/kornelski/rust-security-framework/pull/95/files#diff-0c189053dd842cb8ae78ca32925584a8R1359, since we're using the ClientBuilder rather than manually building an SslContext (which is the one that has a set_peer_id method).

@sfackler
Copy link
Owner

I think I'd be more comfortable if we separated the domain used for hostname verification from the ID used to cache sessions. I'm imagining a new API that let you configure individual connection attempts like the SslConnector::configure method in rust-openssl: https://docs.rs/openssl/0.10.29/openssl/ssl/struct.SslConnector.html#method.configure. For now it would just have set_peer_id used for session caching and connect which would still take the socket and domain for hostname verification.

@sfackler
Copy link
Owner

Also, I'm not sure the naming here should specifically refer to "tickets", since that would refer specifically to one of the two methods TLS provides for session resumption.

@cyang1
Copy link
Author

cyang1 commented May 12, 2020

I think I'd be more comfortable if we separated the domain used for hostname verification from the ID used to cache sessions. I'm imagining a new API that let you configure individual connection attempts like the SslConnector::configure method in rust-openssl: https://docs.rs/openssl/0.10.29/openssl/ssl/struct.SslConnector.html#method.configure. For now it would just have set_peer_id used for session caching and connect which would still take the socket and domain for hostname verification.

That's fair. I believe Security Framework's set_peer_id has further side-effects apart from enabling TLS session tickets though, but I haven't looked into it too closely.

Also, I'm not sure the naming here should specifically refer to "tickets", since that would refer specifically to one of the two methods TLS provides for session resumption.

The changes here are only meant to support client-side stateless TLS resumption. I haven't looked into server-side TLS resumption much, but I had thought that it was implicitly taken care of by each TLS implementation.

@sfackler
Copy link
Owner

I haven't looked into server-side TLS resumption much, but I had thought that it was implicitly taken care of by each TLS implementation.

At least in OpenSSL, the mode of session resumption doesn't affect the API in any significant way. In both cases, the client needs to send something it has saved to the server in the ClientHello.

@cyang1
Copy link
Author

cyang1 commented Mar 26, 2021

Rebased onto current master. Also added a fix for caching the session on Windows, where the session wasn't properly stored if there were underlying async connection abstractions being used.

What can I do to get this across the finish line? For your comment about set_peer_id, I don't think there's any way to make that kind of change in kornelski/rust-security-framework without it being a large API change. I'm also not sure what that change would look like -- given that the macOS SSLSetSessionTicketsEnabled has no effect without SSLSetPeerID, it would require tying the session ticket API in kornelski/rust-security-framework to each domain you want to connect to, rather than the connection itself, which seems worse for usability purposes.

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.

2 participants