Skip to content

Conversation

@djc
Copy link
Member

@djc djc commented Apr 19, 2018

This is what I've been using with my QUIC implementation; seems to work.

As a next step, we could pull out the TransportParameters code again, and I could just pass in an UnknownExtension, but I wanted to run this by you first.

The clone is a bit ugly, but I'm not sure there is a better way right now. I suppose there could be a second ext_refs that copies a ref from the locally-created extensions, which we could then add the externally-added extensions to. There is also the risk of code size bulking from the extra impls, not sure how concerning that is to you.

@djc
Copy link
Member Author

djc commented Apr 19, 2018

(Oh, and I should probably add at least one test. But would like your feedback first.)

@coveralls
Copy link

coveralls commented Apr 19, 2018

Coverage Status

Coverage increased (+0.08%) to 96.701% when pulling 19ebd17 on djc:quic into 3036b13 on ctz:master.

@ctz
Copy link
Member

ctz commented Apr 20, 2018

I have a couple of problems with this, I'm afraid:

  1. I don't really like the ability to add arbitrary client extensions. Mainly because there are almost zero standard extensions which are "fire and forget" like this (ie, can be put in the ClientHello with no further action). Most will actually break the connection:
  • any that rustls sends already will break the handshake (they have to be unique),
  • any that trigger different behaviour if accepted by the server,
  • some can downgrade the connection security (like truncated_hmac, but admittedly this isn't relevant for ciphersuites supported by rustls).
  1. From my reading of draft-ietf-quic-tls-11 and draft-ietf-quic-transport-11 you'll also need to access the server extension to act on the negotiated QUIC version/parameters, and this API doesn't help with that.

I still think I prefer a QUIC-specific trait which encompasses the whole interface between rustls and a QUIC transport :)

@djc
Copy link
Member Author

djc commented Apr 23, 2018

Fair enough! I'll rework my patches in this direction and see where we end up. I don't quite see why the QUIC-specific thing would have to be trait rather than just a struct, but I'll iterate and we can see where we end up.

Given that you want to encapsulate QUIC-specific TLS needs in rustls, I'm a little confused by your decision that you didn't want the TransportParameter enum to live in rustls, though... If the QUIC-specific parts of TLS live in rustls (rather than rustls exposing TLS primitives QUIC can use), then I think it also makes sense to have everything live together.

Perhaps this also has to do with my confusion about the trait, though.

@djc djc force-pushed the quic branch 3 times, most recently from 589d653 to c0ab642 Compare April 24, 2018 13:48
@Ralith
Copy link
Contributor

Ralith commented Apr 24, 2018

I still think I prefer a QUIC-specific trait which encompasses the whole interface between rustls and a QUIC transport :)

I don't think this is a good way to go. This exposes rustls to ongoing breaking changes in the QUIC spec, and likely represents a larger and less reusable body of code than merely exposing extension points as other TLS implementations do. The design intention of QUIC is that TLS be embedded within QUIC, not vis versa.

@ctz
Copy link
Member

ctz commented Apr 28, 2018

Fair enough. I haven't been paying much attention to QUIC, and I'm happy to learn from the knowledge of someone who has :)

I had originally thought that QUIC was already "done" and had been deployed for several years, but now I see that it's only just being standardised.

So I'm reasonably happy with these changes, on the basis that:

  • any/all changes of behaviour/public API are behind the "quic" feature, as they are now.
  • we take advantage of as much of the QUIC-specific tests in bogo as possible, so all code in rustls can be tested without reference to other crates.

@djc
Copy link
Member Author

djc commented Apr 28, 2018

I'm not quite sure how to check what needs to be triggered in bogo, and how it can be. From quickly searching the bogo code on GitHub, it seems that it's mostly limited to checking for the transport parameters extension in the form of the code (26) and that it's followed by a byte array. In my current thinking, I think it'd make more sense to remove the transport parameters-related code from rustls again, and instead use the "extra exts" mechanism with an UnknownExtension. That way, most of the transport parameter abstraction can live elsewhere, but it should be enough to use the bogo stuff that appears to be there.

@Ralith
Copy link
Contributor

Ralith commented Apr 28, 2018

It should also be easy (and otherwise necessary) for us as QUIC implementers to do the write_tls, process_new_packets, read_tls dance ourselves, so I don't think there's a need for the quic::ClientSession/quic::ServerSession interfaces proposed by the current form of this PR at all.

@djc
Copy link
Member Author

djc commented Apr 29, 2018

I disagree with that assessment. As @ctz has already stated, passing in random extensions is not really an activity that any API user should just do. It makes sense to have the usage of these tightly-coupled general mechanisms (passing in an extension during the handshake, extracting secrets) shielded by a specific API. Plus, I think it makes for these things to be reviewed as part of the TLS crate.

In that sense, I think in terms of coupling it's sensible to have the QUIC TLS draft live in rustls (preffed off by default, if @ctz can live with that), exposing only something more like the high-level interface which is discussed in the QUIC TLS draft as API.

@Ralith
Copy link
Contributor

Ralith commented Apr 30, 2018

I'm not opposed to limited-use APIs being masked off by default, but I still don't think it makes sense to stuff a bunch of QUIC details into an otherwise protocol-agnostic TLS library. In addition to the other issues I've highlighted, this could easily place us in a situation where a QUIC implementation can't be updated on crates.io for draft changes or future new major versions until a breaking(!) rustls change is published, which will presumably be rare in the future.

This is particularly true when the QUIC-specific code seems to (almost?) entirely be built on top of operations already exposed by the public API, meaning it would be entirely possible for it to live in a different crate with its own release schedule. That crate could be rustls-quic instead of a full QUIC implementation if you like; I certainly wouldn't mind being able to share at least that portion of our implementations.

Regarding the details of the QUIC-specific APIs, would it make sense to, instead of supplying raw keying material, provide e.g. ring::OpeningKey/SealingKey values directly, preventing the QUIC implementation from needing to worry about looking up the negotiated cipher suite and mapping it to the appropriate primitives?

@djc
Copy link
Member Author

djc commented May 3, 2018

I think this could now benefit from another review round. I've substantially reduced the amount of QUIC-specific code, leaving just the basic mechanisms to pass in transport parameters (as pre-encoded bytes) and extract them again. This is done by some thin wrappers around ClientSession/ServerSession that use the Deref traits to provide access to the underlying session types.

for ext in handshake.extra_exts.iter() {
exts.push((*ext).clone());
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Could this be exts.extend(handshake.extra_exts.iter().cloned()), or perhaps even exts.extend(extra_exts.drain(..))?

Copy link
Member

Choose a reason for hiding this comment

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

This is a fine idea; another alternative is:

-    for ext in handshake.extra_exts.iter() {
-        exts.push((*ext).clone());
-    }
+    let extra_exts = mem::replace(&mut handshake.extra_exts, vec![]);
+    exts.extend(extra_exts);

This means we can drop the commit adding Clone too.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was under the impression the client might need to resend the transport parameters if it has to send another ClientHello in response to a HelloRetryRequest. If that's not the case, then it indeed makes sense to drain extra_exts and get rid of the added Clone implementations.

Copy link
Member Author

Choose a reason for hiding this comment

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

By the way, I have adopted the exts.extend(handshake.extra_exts.iter().cloned() idiom.

Copy link
Member

@ctz ctz left a comment

Choose a reason for hiding this comment

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

Looks good to me. Is this enough to get QUIC basically working?

}
}

pub fn get_params_extension(&self) -> Option<Vec<u8>> {
Copy link
Member

Choose a reason for hiding this comment

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

Let's call this get_quic_params_extension?

Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed.

}
}

fn get_params_extension(&self) -> Option<Vec<u8>> {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe get_quic_params_extension?

Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed.

@Ralith
Copy link
Contributor

Ralith commented May 7, 2018

I detailed a list of requirements for baseline usability in an earlier comment. Of those, the main thing I don't see right now is a convenient way to extract the negotiated handshake digest and symmetric cipher of the session.

@djc
Copy link
Member Author

djc commented May 7, 2018

As for "extract the negotiated handshake digest and symmetric ciper", I was under the impression the export_keying_material() (docs) API is good enough to get the 1-RTT secret out, combined with get_negotiated_ciphersuite() API (docs) should be enough to do decryption/encryption, but I haven't gotten this to interop yet with some basic testing.

@Ralith
Copy link
Contributor

Ralith commented May 8, 2018

get_negotiated_ciphersuite

That API does, strictly, expose the needed information, but as previously discussed, requiring QUIC implementations to painstakingly match on the CipherSuite and HashAlgorithm when rustls already has a mapping to the actual ring primitives is a maintenance hazard, and is duplication of code.

Another issue with wrapping the session objects in QUIC-specific types is that they cannot be converted into a Box<Session>. This could be fixed by manually writing out a forwarding implementation of Session for them, or by doing away with the QUIC-specific types entirely.

We're also still missing #151, aren't we?

@djc
Copy link
Member Author

djc commented May 8, 2018

That API does, strictly, expose the needed information, but as previously discussed, requiring QUIC implementations to painstakingly match on the CipherSuite and HashAlgorithm when rustls already has a mapping to the actual ring primitives is a maintenance hazard, and is duplication of code.

The SupportedCipherSuite returned by get_negotiated_ciphersuite() gives you access to the relevant ring::aead::Algorithm and ring::digest::Algorithm through get_aead_alg() and get_hash().

Another issue with wrapping the session objects in QUIC-specific types is that they cannot be converted into a Box. This could be fixed by manually writing out a forwarding implementation of Session for them, or by doing away with the QUIC-specific types entirely.

With the Deref and DerefMut implementations provided in this PR, you can use where T: DerefMut + Deref<Target = S>, S: Session to get direct access to the Session methods through QuicClientTls and QuicServerTls.

#151 indeed isn't there yet, but in my opinion it shouldn't necessarily block this PR from moving forward, as it is not strictly necessary to get QUIC sessions off the ground.

@djc
Copy link
Member Author

djc commented May 8, 2018

I've just solved the interop problem on my implementation's side, so I believe the PR that's there is enough to establish at least some kind of flowing data ("basically working").

@Ralith
Copy link
Contributor

Ralith commented May 9, 2018

The SupportedCipherSuite returned by get_negotiated_ciphersuite() gives you access to the relevant ring::aead::Algorithm and ring::digest::Algorithm through get_aead_alg() and get_hash().

Oh, sweet!

With the Deref and DerefMut implementations provided in this PR, you can use where T: DerefMut + Deref<Target = S>, S: Session to get direct access to the Session methods through QuicClientTls and QuicServerTls.

I'm not sure how that addresses the problem of not being able to create a boxed Session trait object from it.

@djc
Copy link
Member Author

djc commented May 9, 2018

I'm not sure what exactly you need Box<Session> for, but I'd guess you can do much the same thing with Box<T> where T: DerefMut + Deref<Target = S>, S: Session.

@Ralith
Copy link
Contributor

Ralith commented May 10, 2018

That's not a trait object. Trait objects allow you to use values of different types in monomorphic code by dynamic dispatch, and are useful when you specifically need type erasure. For example, my QUIC impl does not distinguish between client and server at the type level. This works great with OpenSSL and with rustls's current API because they expose agnostic Ssl and Box<Session> types respectively, but the API in this PR defeats that, requiring awkward boilerplate to work around.

@djc
Copy link
Member Author

djc commented May 13, 2018

@ctz what's your take? I can do more work on this, but I'd like your feedback before doing so.

@ctz
Copy link
Member

ctz commented May 13, 2018

I'd really like to see the bogo tests QUICTransportParams-Client-TLS13Draft23 and QUICTransportParams-Server-TLS13Draft23 running before merging this. These are pretty straightforward tests, and just check the transport parameters extension is being passed in and out properly. I don't mind doing the plumbing to make those work, let me know.

On the subject of Box<Session>, this is actually needed for bogo_shim to work -- see https://github.com/ctz/rustls/blob/32eeec61d3bd63fc8eafca39fb6e5807c448076f/examples/internal/bogo_shim.rs#L665

Perhaps, all under cfg(feature = "quic"), we could have just:

  • fn ClientSession::new_quic()
  • fn ClientSession::get_quic_transport_parameters()
  • fn ServerSession::new_quic()
  • fn ServerSession::get_quic_transport_parameters()

@djc
Copy link
Member Author

djc commented May 13, 2018

I was actually thinking along similar lines, although organization-wise I thought we might do it as an extension trait that still lives in the quic module. That way, you also have to explicitly scope in those traits to make them available, which seems nice to me. Does that sounds like a workable approach?

use webpki;

/// Generic methods for QUIC sessions
pub trait QuicExt {
Copy link
Contributor

Choose a reason for hiding this comment

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

If this was pub trait QuicExt: Session { ... }, then Box<QuicExt> would be useful. I don't think I need this, and it's not a paradigm that composes well if we end up with various other extension traits, but it's easy.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, if we instead had pub trait Session: QuicExt, the composability problem is solved and I think the visibility story remains the same! Might need some gymnastics to #[cfg] out gracefully (say, an indirection through another trait with a blanket impl, or make the extension trait always-present but cfg its members), but should be doable.

@ctz
Copy link
Member

ctz commented May 13, 2018

That looks like a good improvement to me!

@djc
Copy link
Member Author

djc commented May 13, 2018

Okay, so like this? I find the inverted trait bounds a bit counter-intuitive, but it does seem to work.

Copy link
Contributor

@Ralith Ralith left a comment

Choose a reason for hiding this comment

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

That's about what I had in mind, yeah. I agree it's a little weird but it solves the general problem of composable session extension traits tidily.

src/lib.rs Outdated
}

#[cfg(not(feature = "quic"))]
trait QuicExt {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Unintended duplicate?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup, fixed that now.

@djc
Copy link
Member Author

djc commented May 14, 2018

@ctz what do you think? Would appreciate it if you can take a look at the bogo stuff.

@ctz
Copy link
Member

ctz commented May 14, 2018

I'll look at that now.

ctz added 2 commits May 14, 2018 20:29
- bogo_shim needs quic feature
- provide/check quic transport params in bogo_shim
- reject servers that handshake at TLS1.2, but include a quic transport
  params extension.
- don't expose quic transport params extension for TLS1.2 clients.

These last two match BoringSSL.
@ctz ctz merged commit dd765a9 into rustls:master May 14, 2018
@djc
Copy link
Member Author

djc commented May 14, 2018

Thanks!

@djc djc deleted the quic branch May 14, 2018 20:03
@djc
Copy link
Member Author

djc commented May 14, 2018

@ctz would you mind rebasing your draft-28 branch on top of current master? That makes testing a bit more convenient for me.

@ctz
Copy link
Member

ctz commented May 15, 2018

Thanks to you too. I've rebased that branch.

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.

4 participants