Subject: Re: [TLS] ALPS and TLS 1.3 half-RTT data

From: David Benjamin <[email-masked]>
Date: Fri, 29 Jan 2021 18:38:08 -0500

Hi all,

Thanks all for the feedback. I’ve tried to address it below, but there's a
lot of text, so please let me know if I’ve missed or misunderstood any of
your points.

Cory commented on SETTINGS_[HQ]PACK_ENABLE_STATIC_TABLES in
draft-vvv-httpbis-alps-00. I agree that is odd. We’ve uploaded a draft-01
that drops it.

Cory also wrote:

> I think the document does a good job laying out the difficulties with

> half-RTT data, but it didn't convince me that ALPS is easier for H2.

To clarify, are you unconvinced that ALPS is easier than leaving H2 alone,
or that ALPS is easier than solving this problem with half-RTT? The
document’s aim is the latter. Your comment in Martin’s thread
<https://lists.w3.org/Archives/Public/ietf-http-wg/2020OctDec/0273.html>
reads to me like you agree with this. Am I interpreting that correctly? (I
think draft-thomson-httpbis-h2-0rtt roughly corresponds to Section 2.3 of
my document. Something like it would be necessary, but not sufficient, to
solve this with half-RTT.)

As to leaving H2 alone, doing nothing is indeed generally easier than doing
something. But this is perhaps not a useful criteria. :-) The question is
what’s the benefit of solving the problem. My interest is in the privacy
benefits of rethinking content negotiation
<https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability-02>.
Victor has use cases around WebTransport. The document cites some other uses
<https://tools.ietf.org/html/draft-davidben-tls-alps-half-rtt-00#section-1>.

> My biggest concerns are around the need to tightly couple the TLS and

> application layer stacks.

I agree this adds a non-I/O TLS interaction, but adding interactions isn’t
new. Many HTTP mechanisms touch TLS:

   - RFC8473 <https://tools.ietf.org/html/rfc8473> uses TLS exporters.
   - RFC5929 <https://tools.ietf.org/html/rfc5929> specifies various TLS
   channel bindings interfaces for authentication protocols.
   - RFC8470 <https://tools.ietf.org/html/rfc8470> integrates with the
   0-RTT/1-RTT transition point.
   - ALPN <https://tools.ietf.org/html/rfc7301> itself uses TLS to select
   variations on HTTP.
   - Section 9.2.2 of RFC7540
   <https://httpwg.org/specs/rfc7540.html#rfc.section.9.2.2> specifies
   cipher suites for HTTP/2.
   - HTTP/2 cross-name pooling <https://httpwg.org/specs/rfc7540.html#reuse>
   and HTTP’s general notion of authority
   <https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#rfc.section.4.3.3>
   are tied to the TLS certificate.
   - Applications using client certificates care about the relation between
   TLS-level authentication and HTTP messages. The web even has a notion of
   “uncredentialed” HTTP fetches which shouldn’t send client certificates.
   - Secondary certificates
   <https://tools.ietf.org/html/draft-ietf-httpbis-http2-secondary-certs-06>
   use TLS exported authenticators
   <https://tools.ietf.org/html/draft-ietf-tls-exported-authenticator-13>.

Systems and thus their problems span components. The question is how best
to split a solution across those components. The aim with ALPS is to
minimize coupling while still getting settings for the first client write.
We can build something piecemeal with half-RTT, ticket state, and early
data callbacks. Or we can abstract a notion of “protocol settings”,
configured and surfaced at well-defined points. I prefer the latter.

As to the complexity, I think you may be overestimating it. It sounds like
your model has three components: TLS, HTTP/2, and some ALPN dispatcher. And
your concern is complexity in HTTP/2. Is that right? ALPS should slot next
to ALPN processing at the same points. For example:

   - The dispatcher already must know which ALPN protocols are supported
   and how to instantiate them.
   - Extend it so protocols can optionally be ALPS-aware. An ALPS-aware
   protocol has a settings parameter in the instantiation function. It also
   configures settings to send. This all happens at the same time as
   existing ALPN setup.
   - The dispatcher runs the TLS handshake and gets an ALPN protocol as
   before, plus now an ALPS value.
   - The dispatcher instantiates the protocol as before, but if ALPS was
   negotiated, also passes a byte string to the ALPS-aware handler. Note the
   extension ensures this can only happen if the protocol was configured as
   ALPS-aware above.

The protocol acts accordingly. If ALPS was negotiated, HTTP/2 would apply
the received settings to the initial peer state. It also knows the initial
local state is different and can skip sending some values. This is added
logic to HTTP/2, but I think it’s fairly minimal. (And we can certainly
figure out the exact details that would work best.) It even has precedent
in the HTTP Upgrade path
<https://httpwg.org/specs/rfc7540.html#Http2SettingsHeader> for “http”
URLs, so it's not even really new. Also note that all this happens before
any of the usual application I/O. (I wasn't sure what you meant by "timing
issues". Could you elaborate? I read it to mean where the integration
points were, so hopefully the example above helps clarify. Also note that
all of the logic above is synchronous. We don't need new points in state
machines to wait for data.)

> It is also different from making the existing logic conditional, as

> presumably we wouldn't bother with SETTINGS ACKs for ALPS settings.

Our draft currently says to omit it. That matches HTTP2-Settings and seems
simplest. I'm not sure I see how that's different from making some logic
conditional.

> I want to stress: I don't think ALPS is a bad idea, I think it's a

> good one! It clearly brings benefits for protocols that can require

> its presence. If we want to mint a new ALPN token for HTTP/2 that also

> mandates ALPS support I'm open to that, but frankly without it I don't

> see that ALPS helps us much for HTTP/2.

You’re right that HTTP/2 would need ALPS and non-ALPS codepaths. I think
that's unavoidable. The 0.5-RTT version would also need two codepaths since
TLS 1.2 lacks 0.5-RTT. If folks feel this is worth an “h2.1” ALPN token,
that’s fine by me. I think that’s largely an aesthetics question and
doesn't have much practical complexity difference: HTTP/2 and HTTP/2.1
implementations will presumably share code, so this is largely switching if
(alps) to if (h2_1).

Martin wrote:

> The idea of creating a 0.5-RTT -> 1-RTT marker somehow is an odd idea,
and one we've talked about maybe using in a new version of TLS, but I
hardly see justification for that here.

To be clear, I have no interest in a marker. The marker would be needed as
part of the 0.5-RTT alternate (so the client knows when to give up waiting
for SETTINGS and start the protocol) which I do not favor. I think ALPS is
a better rationalization of 0.5-RTT data.

> A lot of your reluctance to use half-RTT seems to be based on the buggy
IO pattern you identified.  Coupling send and receive flow control in the
way you describe is - at least in my view - a defect in stacks rather than
something to engineer a protocol around.

I think you may be conflating send/receive dependencies with
application/handshake dependencies. The former is indeed incompatible with
HTTP/2 (though more common in simpler protocols like HTTP/1.1). But this is
about the latter, which is, in general, a security requirement and
part of RFC8446,
section 4.4.4 <https://tools.ietf.org/html/rfc8446#section-4.4.4>. Where
we’ve relaxed this (False Start, 0-RTT, 0.5-RTT), we’ve needed to work
through the implications carefully and usually introduce caveats.
Critically, in 0.5-RTT, the client is not yet authenticated.

To tie back to Cory’s comments about coupling between HTTP and TLS, these
caveats often require a more complex interface to capture them. That’s
sometimes necessarily but, here, I think we can make a safer and simpler
interface with ALPS.

> What is somewhat odd about TLS 1.3 is the way that applications can write
to the socket before the handshake is complete.  But reads are only
possible afterwards.  That does create some problems for incoming data, but
it is always a modest amount to buffer.  A new version of TLS might even
allow clients to limit 0.5-RTT data, if this turns into a real problem.

I’m not sure I follow where you envision buffering data. Are you saying the
client should, in parallel, eagerly decrypt and buffer application data
while sending C/CV/Fin, just in case the server is blocking the read of
C/CV/Fin on half-RTT write? That seems backwards to me. Either way, one
side needs to perform parallel handshake/application I/O. Having the server
do it makes more sense since TLS is already responsible for limits on the
handshake flight. (It’s also unclear to me if section 4.4.4 of RFC8446
allows the client to decrypt messages early like that.)

Either way, it’s a question of how complex the application/TLS interface
needs to be. If you say the connection is “ready” earlier, you can use the
existing I/O interface, but client certificates assumptions break and the
application needs tighter integration for when that’s ready. If you do that
with separate write functions, now you need extra integration into the
application’s I/O path. If you try to preconfigure the 0.5-RTT data
statically, the pre-ready “handshake” state needs to drive parallel
transport I/O, which, in non-blocking APIs, means tighter integration
between TLS and the application’s event loop.

Or if you do a more traditional-looking TLS extension like ALPS or ALPN,
you can just follow the existing patterns. Given the integration points a
solution needs to hit anyway (ticket integration for 0-RTT), I think this
is a worthwhile abstraction to reduce complexity across the TLS/application
boundary.

How does NSS expose the late client authentication to the application? I
thought NSS didn't support half-RTT at all when the server requests client
certificates, but perhaps I misunderstood.

David

---

From: "Martin Thomson" <[email-masked]>
Date: Mon, 01 Feb 2021 11:14:28 +1100

On Sat, Jan 30, 2021, at 10:38, David Benjamin wrote:
> How does NSS expose the late client authentication to the application? 
> I thought NSS didn't support half-RTT at all when the server requests 
> client certificates, but perhaps I misunderstood.

There are three states we use with respect to client authentication; those guide what might be done:

1. No CertificateRequest (0.5-RTT is therefore always safe to send)
2. Request a client certificate, but don't require it (positions differ here)
3. Require a client certificate (0.5-RTT is arguably unsafe to send, unless you can be sure it is not conditional on client authentication)

I originally decided to allow 0.5-RTT in cases 1 and 2.  My logic was that a server in that posture always needed to check for a certificate before sending anything.  That is, the server was already in the appropriate posture and enabling 0.5-RTT could not make things worse.  However, Ekr was unhappy with that idea and I decided not to fight it (that doesn't make him any more right, but he was definitely right to insist on stronger justification; I just didn't have the energy to provide that justification, more so because we don't really make a server and all I really wanted to know was that we correctly handled 0.5-RTT at the client).

Back to the main subject...

On reflection, the buffering thing was a false trail.  The reason we have buffering for 0-RTT is primarily because that is followed by handshake-critical data.  We have to clear 0-RTT aside to get to handshake data, because there is a risk that it fills the congestion window and prevents the Finished from ever being sent.  Unless you support blocking writes that also block reads, I don't believe that 0.5-RTT has the same problem.

For me, this whole question is down to imposing constraints on usage.  We have this artificial constraint that says an extension or handshake message (like ALPS) can't contain anything that is conditioned on application data.  That is a useful constraint, because it makes the handshake easier to reason about.  But the same constraint can be applied to 0.5-RTT if we chose, making all versions of it safe to use in the same way (even case 3 above).

At that point, the question becomes whether you think that covering this data with the handshake transcript is worth the head-of-line blocking exposure.  (And other miscellany, I guess, like what your priors are regarding adding more extension points.)