Subject: [websec] HSTS: max-age=0 interacting with includeSubdomains

From: David Keeler <[email-masked]>
Date: Mon, 13 Aug 2012 15:16:06 -0700

Hello,

The current HSTS spec draft says "A max-age value of zero (i.e.,
"max-age=0") signals the UA to cease regarding the host as a Known HSTS
Host." (section 6.1.1) How does this interact with the includeSubdomains
directive?
For instance, if the UA receives an HSTS header with includeSubdomains
from example.com but then receives an HSTS header with max-age=0 from
sub.example.com, is sub.example.com to be noted as an HSTS host?
Either way, I believe the language of the spec could be a bit more clear.

Cheers,
David Keeler

---

From: Adam Barth <[email-masked]>
Date: Mon, 13 Aug 2012 15:29:24 -0700

The way the implementation in Chrome works is that max-age=0 only
clears the entry for that particular host name.  If there's another
shorter host name with includeSubdomains, that isn't affected.

Adam

On Mon, Aug 13, 2012 at 3:16 PM, David Keeler <[email-masked]> wrote:
> _______________________________________________
> websec mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/websec

---

From: Tobias Gondrom <[email-masked]>
Date: Sat, 18 Aug 2012 19:58:18 +0100

Hi David and Adam,

that was my understanding from the draft so far, too. See section 6.1.2 
first paragraph.

Having said that, David may be right and we could be more explicit about 
that max-age=0 and includeSubDomains does not ref up in the tree. (for 
example by adding one more example).
It was clear to me from the text, but well I can be too deep in things 
from time to time and take things for granted. Any other opinions on this?

Best, Tobias

---

From: Adam Barth <[email-masked]>
Date: Mon, 20 Aug 2012 17:19:31 -0700

On Mon, Aug 20, 2012 at 5:09 PM, Brian Smith <[email-masked]> wrote:
> Adam Barth wrote:
> Let me try to clarify with an example, which I think should be explicitly documented in the spec:
>
> 1. We visit https://example.com and receive HSTS with max-age=1234567890 ; includeSubdomains
> 2. We visit https://sub.example.com and receive HSTS with max-age=0
>
> Now, is sub.example.com HSTS (because it inherits the HSTS setting from example.com), or is it non-HSTS (because we received a max-age=0 for that host)?

It is HSTS.  Visit (2) would have removed any sub.example.com-specific
entries in the HSTS state table, but there were none, so it didn't
have any effect on the world.

> When we receive a HSTS header from sub.example.com with max-age > 0, then we will treat the expiration time of sub.example.com to be MAX(expiration(example.com), expiration(sub.example.com)).

A simpler way to think about it is that you keep state for each host.
To answer the question of whether a given host is HSTS, you walk the
list of subdomains and check their unexpired state.

> But, if we receive a HSTS header from sub.example.com with max-age == 0, there are two possibilities:
>
> 1. We act consistently with the above case and calculate the HSTS expiration time of sub.example.com to be:
>
>        MAX(expiration(example.com), expiration(sub.example.com))
>     == MAX(expiration(example.com), 0)
>     == expiration(example.com)
>
>    This means that we would effectively be ignoring the max=age == 0 value sent by sub.example.com.

Correct.

> 2. We honor the max-age=0 directive sent by sub.example.com and turn off HSTS for sub.example.com, but not for example.com. That is, we would treat max-age == 0 from a subdomain as "do not do includeSubdomains inheritance for this subdomain."

This is not correct.  We do "honor" the max-age directive set by
sub.example.com by expiring any sub.example.com-specific state.
However, sub.example.com is HSTS due to state specific to example.com,
which is not expired.

> Now, it seems to me that the only reasonable choice is #2, but the spec seems to imply that we should do #1.

Choice #1 seems reasonable to me.

> Here's the problematic scenerio:
>
> 1. We visit https://example.com and get the HSTS header with includeSubdomains and an (effectively) infinite expiration time.

There is no such thing as an infinite expiration time.  Let's proceed
assuming you mean "one year" rather than infinite.

> 2. The owners of example.com decides to turn of HSTS for whatever reason (perhaps the domain changed owners, or there's a compatibility issue, or whatever), so they start sending out HSTS with max-age=0 for example.com and for all the subdomains.

That's not a correct way of disabling HSTS after (1).  Instead, they
need only send out an max-age=0 header for example.com itself.

> 3. We visit https://sub.example.com and get the max-age=0 HSTS header.
>
> If we choose choice #2, we do what the owners of example.com intended, by treating https://sub.example.com as non-HSTS right away, without ever needing to visit https://example.com, which we might NEVER do ever again.

I mean, it's just guesswork as to what they intend.  I can write a
similar story in which the intent is the reverse.

> If we choose choice #1, we will effectively be making https://sub.example.com HSTS forever, and the domain owner has no way to help us undo it.

They can simply initiate a request to https://example.com/ (e.g., by
using an HTTP redirect or an HTML image element) and clear the HSTS
state for that host name.

Adam

---

From: Tobias Gondrom <[email-masked]>
Date: Tue, 21 Aug 2012 10:31:44 +0100

> state for that host name.

<hat="individual">

Would agree with Adam.

And for Brian, I think there is actually one more use case that you 
haven't considered:
Look at it in reverse order:
1. We visit https://sub.example.com and receive HSTS with max-age=1234567890
2. We visit https://example.com and receive HSTS with max-age=0 ; 
includeSubdomains

as far as I remember that would actually clear HSTS for sub.example.com?
So your mathematical formula above would not reflect this correctly:
"
       MAX(expiration(example.com), expiration(sub.example.com))
     == MAX(expiration(example.com), 0)
     == expiration(example.com)
"

Correct, or am I missing something here?

Best, Tobias

---

From: Adam Barth <[email-masked]>
Date: Tue, 21 Aug 2012 13:40:27 -0700

On Tue, Aug 21, 2012 at 1:38 PM, Brian Smith <[email-masked]> wrote:
>> Adam Barth wrote:
>> >Brian Smith wrote:
>> >> 2. The owners of example.com decides to turn of HSTS for whatever
>> >> reason (perhaps the domain changed owners, or there's a
>> >> compatibility issue, or whatever), so they start sending out HSTS
>> >> with max-age=0 for example.com and for all the subdomains.
>
>> > That's not a correct way of disabling HSTS after (1).  Instead,
>> > they need only send out an max-age=0 header for example.com itself.
>
> [...]
>
>> > They can simply initiate a request to https://example.com/ (e.g.,
>> > by using an HTTP redirect or an HTML image element) and clear the
>> > HSTS state for that host name.
>
> I understand what you're saying and it makes sense. And, I agree that in a web browser that is a pretty reasonable way to handle some emergency where you have to turn off HSTS for some reason, though I think it would be quite tricky to do so in a way that is reliable.
>
> Another thing to keep in mind is that, in order to turn off HSTS, the site must comply with the browser's requirements for HSTS sites anyway; otherwise the browser will ignore your HSTS header and avoid doing the redirect or avoid loading the page with the img tag in it.
>
> FWIW, in Firefox we are also going to honor max-age=0 as a mechanism to disable the entries in our pre-loaded HSTS list that will ship in the browser.

How long do you plan to cache the disable?

Adam

---

From: Adam Barth <[email-masked]>
Date: Tue, 21 Aug 2012 15:41:13 -0700

On Tue, Aug 21, 2012 at 2:30 PM, Brian Smith <[email-masked]> wrote:
> Adam Barth wrote:
>> > FWIW, in Firefox we are also going to honor max-age=0 as a
>> > mechanism to disable the entries in our pre-loaded HSTS list that
>> > will ship in the browser.
>>
> Initially: until we receive an HSTS header with max-age > 0 for the site, or until the user clears the dynamic HSTS database in a way that removes the dynamic HSTS information (e.g. by using "Clear Recent History"), to reset back to the "as shipped" state.

Interesting.  I wonder if that's something Chrome should do as well.
Let me ask agl for his thoughts.

Thanks,
Adam