Skip to content

Commit

Permalink
Merge pull request #220 from xyaoinum/main
Browse files Browse the repository at this point in the history
Fix spec: use "registrable domain" for caller context
  • Loading branch information
xyaoinum authored Jul 19, 2023
2 parents a761ee5 + fcadb73 commit 948a26b
Showing 1 changed file with 39 additions and 33 deletions.
72 changes: 39 additions & 33 deletions spec.bs
Original file line number Diff line number Diff line change
Expand Up @@ -129,24 +129,28 @@ spec: html; urlPrefix: https://www.rfc-editor.org/rfc/
- <dfn for="epoch">taxonomy version</dfn>: a string.
- <dfn for="epoch">model version</dfn>: a string.
- <dfn for="epoch">config version</dfn>: a string.
- <dfn for="epoch">top 5 topics with caller origins</dfn>: a list of [=topic with caller origins=].
- <dfn for="epoch">top 5 topics with caller domains</dfn>: a list of [=topic with caller domains=].
- <dfn for="epoch">time</dfn>: a {{DOMHighResTimeStamp}} (from Unix epoch).

A <dfn for="browsing topics types">topic with caller origins</dfn> is a struct with the following fields:
- <dfn for="topic with caller origins">topic id</dfn>: an integer.
- <dfn for="topic with caller origins">caller origins</dfn>: a set of [=origins=].
A <dfn for="browsing topics types">topic with caller domains</dfn> is a struct with the following fields:
- <dfn for="topic with caller domains">topic id</dfn>: an integer.
- <dfn for="topic with caller domains">caller domains</dfn>: a set of [=domains=].

A <dfn for="browsing topics types">topics history entry</dfn> is a struct with the following fields and default values:
- <dfn for="topics history entry">document id</dfn>: an integer, default to 0.
- <dfn for="topics history entry">topics calculation input data</dfn>: a string, default to an empty string.
- <dfn for="topics history entry">time</dfn>: a {{DOMHighResTimeStamp}} (from Unix epoch).
- <dfn for="topics history entry">topics caller origins</dfn>: an ordered set of [=origins=], default to an empty set.
- <dfn for="topics history entry">topics caller domains</dfn>: an ordered set of [=domains=], default to an empty set.

A <dfn for="browsing topics types">topics caller context</dfn> is a struct with the following fields:
- <dfn for="topics caller context">caller origin</dfn>: an [=origin=].
- <dfn for="topics caller context">caller domain</dfn>: a [=domain=].
- <dfn for="topics caller context">top level context domain</dfn>: a [=domain=].
- <dfn for="topics caller context">timestamp</dfn>: a {{DOMHighResTimeStamp}} (from Unix epoch).

<div class="note">
All [=domains=] used in this API will be result of obtaining the [=registrable domain=] from some [=host=].
</div>

<h2 id="user-agent-associated-state-header">User agent associated state</h2>
Each [=user agent=] has an associated [=browsing topics types/user topics state=] <dfn for="user agent">user topics state</dfn> with [=user topics state/epochs=] initially empty, and [=user topics state/hmac key=] initially a randomly generated 128 bit number.

Expand Down Expand Up @@ -218,15 +222,15 @@ spec: html; urlPrefix: https://www.rfc-editor.org/rfc/
1. [=list/Append=] |topicsHistoryEntry| to user agent's [=user agent/topics history storage=].
</div>

<h2 id="collect-topics-caller-origin-header">Collect topics caller origin</h2>
<h2 id="collect-topics-caller-domain-header">Collect topics caller domain</h2>
<div algorithm>
To <dfn>collect topics caller origin</dfn>, given a {{Document}} |document| and an [=origin=] |callerOrigin|:
To <dfn>collect topics caller domain</dfn>, given a {{Document}} |document| and a [=domain=] |callerDomain|:

1. Run the following steps [=in parallel=]:
1. Let |documentId| be |document|'s [=document-id-header/document id=].
1. If user agent's [=user agent/topics history storage=] does not contain a [=topics history entry=] whose [=topics history entry/document id=] is |documentId|, return.
1. Let |topicsHistoryEntry| be the [=topics history entry=] in user agent's [=user agent/topics history storage=] whose [=topics history entry/document id=] is |documentId|.
1. [=set/Append=] |callerOrigin| to |topicsHistoryEntry|'s [=topics caller origins=].
1. [=set/Append=] |callerDomain| to |topicsHistoryEntry|'s [=topics caller domains=].
</div>

<h2 id="derive-top-5-topics-header">Derive top 5 topics</h2>
Expand Down Expand Up @@ -305,27 +309,27 @@ spec: html; urlPrefix: https://www.rfc-editor.org/rfc/
1. For each |topicId| in |topicIds|:
1. If |topicsCallers|[|topicId|] does not exist:
1. Initialize |topicsCallers|[|topicId|] to be an empty [=list=].
1. For each |callerOrigin| in |topicsHistoryEntry|'s [=topics history entry/topics caller origins=]:
1. [=list/Append=] |callerOrigin| to |topicsCallers|[|topicId|].
1. For each |callerDomain| in |topicsHistoryEntry|'s [=topics history entry/topics caller domains=]:
1. [=list/Append=] |callerDomain| to |topicsCallers|[|topicId|].
1. Let |top5Topics| be the result of running [=derive top 5 topics=] algorithm, given |historyEntriesForUserTopics|.
1. Let |top5TopicsWithCallerOrigins| be an empty [=list=].
1. Let |top5TopicsWithCallerDomains| be an empty [=list=].
1. For each |topTopicId| in |top5Topics|:
1. Let |topicWithCallerOrigins| be a [=topic with caller origins=] struct with [=topic with caller origins/topic id=] initially 0 and [=topic with caller origins/caller origins=] initially empty.
1. Let |topicWithCallerDomains| be a [=topic with caller domains=] struct with [=topic with caller domains/topic id=] initially 0 and [=topic with caller domains/caller domains=] initially empty.
1. If |topTopicId| is allowed by user preference setting:
1. Set |topicWithCallerOrigins|'s [=topic with caller origins/topic id=] to |topicId|.
1. Set |topicWithCallerDomains|'s [=topic with caller domains/topic id=] to |topicId|.
1. Let |topicWithDescendantIds| be the result of running [=get descendant topics=] given |topTopicId|.
1. Add |topTopicId| to |topicWithDescendantIds|.
1. For each |topicId| in |topicWithDescendantIds|:
1. If |topicId| is allowed by user preference setting:
1. Insert all elements in |topicsCallers|[|topicId|] to |topicWithCallerOrigins|'s [=topic with caller origins/caller origins=].
1. [=list/Append=] |topicWithCallerOrigins| to |top5TopicsWithCallerOrigins|.
1. Insert all elements in |topicsCallers|[|topicId|] to |topicWithCallerDomains|'s [=topic with caller domains/caller domains=].
1. [=list/Append=] |topicWithCallerDomains| to |top5TopicsWithCallerDomains|.

1. Let |epoch| be an [=epoch=] struct with default initial field values.
1. Set |epoch|'s [=epoch/taxonomy=] to user agent's [=user agent/taxonomy=].
1. Set |epoch|'s [=epoch/taxonomy version=] to user agent's [=user agent/taxonomy version=].
1. Set |epoch|'s [=epoch/model version=] to user agent's [=user agent/model version=].
1. Set |epoch|'s [=epoch/config version=] to user agent's [=user agent/configuration version=].
1. Set |epoch|'s [=epoch/top 5 topics with caller origins=] to |top5TopicsWithCallerOrigins|.
1. Set |epoch|'s [=epoch/top 5 topics with caller domains=] to |top5TopicsWithCallerDomains|.
1. Set |epoch|'s [=epoch/time=] to |fromUnixEpochTime|.

1. [=list/Append=] |epoch| to user agent's [=user agent/user topics state=]'s [=user topics state/epochs=].
Expand Down Expand Up @@ -383,16 +387,16 @@ spec: html; urlPrefix: https://www.rfc-editor.org/rfc/
1. Let |epochs| be the result of running the [=calculate the epochs for caller=] algorithm given |callerContext| as input.
1. Let |result| be an empty [=list=].
1. For each |epoch| in |epochs|:
1. If |epoch|'s [=epoch/top 5 topics with caller origins=] is empty (implying the topics calculation failed for that epoch), then continue.
1. If |epoch|'s [=epoch/top 5 topics with caller domains=] is empty (implying the topics calculation failed for that epoch), then continue.
1. Let |topic| be null.
1. Let |topTopicIndexDecisionMessageArray| be the concatenation of "top-topic-index-decision|", |epoch|'s [=epoch/time=], and |callerContext|'s [=topics caller context/top level context domain=].
1. Let |topTopicIndexDecisionHmacOutput| be the output of the [=HMAC algorithm=], given input parameters: whichSha=SHA256, key=user agent's [=user agent/user topics state=]'s [=user topics state/hmac key=], and message_array=|topTopicIndexDecisionMessageArray|.
1. Let |topTopicIndexDecisionHash| be 64 bit truncation of |topTopicIndexDecisionHmacOutput|.
1. Let |topTopicIndex| be |topTopicIndexDecisionHash| % 5.
1. Let |topTopicWithCallerOrigins| be |epoch|'s [=epoch/top 5 topics with caller origins=][|topTopicIndex|].
1. If |topTopicWithCallerOrigins|'s [=topic with caller origins/caller origins=] contains |callerContext|'s [=topics caller context/caller origin=]:
1. Let |topTopicWithCallerDomains| be |epoch|'s [=epoch/top 5 topics with caller domains=][|topTopicIndex|].
1. If |topTopicWithCallerDomains|'s [=topic with caller domains/caller domains=] contains |callerContext|'s [=topics caller context/caller domain=]:
1. Set |topic| to an empty {{BrowsingTopic}} dictionary.
1. Set |topic|["{{BrowsingTopic/topic}}"] to |topTopicWithCallerOrigins|'s [=topic with caller origins/topic id=].
1. Set |topic|["{{BrowsingTopic/topic}}"] to |topTopicWithCallerDomains|'s [=topic with caller domains/topic id=].
1. If |topic| is null, or if |topic|'s {{BrowsingTopic/topic}} is 0 (i.e. the candidate topic was cleared), then continue.
1. Let |randomOrTopTopicDecisionMessageArray| be the concatenation of "random-or-top-topic-decision|", |epoch|'s [=epoch/time=], and |callerContext|'s [=topics caller context/top level context domain=].
1. Let |randomOrTopTopicDecisionHmacOutput| be the output of the [=HMAC algorithm=], given input parameters: whichSha=SHA256, key=user agent's [=user agent/user topics state=]'s [=user topics state/hmac key=], and message_array=|randomOrTopTopicDecisionMessageArray|.
Expand Down Expand Up @@ -438,10 +442,11 @@ spec: html; urlPrefix: https://www.rfc-editor.org/rfc/
<div algorithm="browsingTopics(options)">
The <dfn for="Document" method>browsingTopics(options)</dfn> method steps are:
1. Let |document| be [=this=].
1. Let |topLevelDocument| be |document|'s [=node navigable=]'s [=navigable/top-level traversable=]'s [=navigable/active document=].
1. Let |promise| be [=a new promise=].
1. Let |topicsCallerContext| be a [=topics caller context=].
1. Set |topicsCallerContext|'s [=topics caller context/caller origin=] to |document|'s [=Document/origin=].
1. Set |topicsCallerContext|'s [=topics caller context/top level context domain=] to the result of running |document|'s [=node navigable=]'s [=navigable/top-level traversable=]'s [=navigable/active document=]'s {{Document/domain}} getter steps.
1. Set |topicsCallerContext|'s [=topics caller context/caller domain=] to |document|'s [=Document/origin=]'s [=origin/host=]'s [=host/registrable domain=].
1. Set |topicsCallerContext|'s [=topics caller context/top level context domain=] to |topLevelDocument|'s [=Document/origin=]'s [=origin/host=]'s [=host/registrable domain=].
1. Let |unsafeMoment| be the [=wall clock=]'s [=wall clock/unsafe current time=].
1. Let |moment| be the result of running [=coarsen time=] algorithm given |unsafeMoment| and [=wall clock=] as input.
1. Let |fromUnixEpochTime| be the [=duration from=] the [=Unix epoch=] to |moment|.
Expand All @@ -450,7 +455,7 @@ spec: html; urlPrefix: https://www.rfc-editor.org/rfc/
- |document|'s [=Document/origin=] is an [=opaque origin=].
- |document| is not [=allowed to use=] the <code><a href="#browsing-topics-policy-controlled-feature">browsing-topics</a></code> feature.
- |document| is not [=allowed to use=] the <code><a href="#interest-cohort-policy-controlled-feature">interest-cohort</a></code> feature.
- The user preference setting disallows the access to topics from |topicsCallerContext|'s [=topics caller context/caller origin=] or |topicsCallerContext|'s [=topics caller context/top level context domain=].
- The user preference setting disallows the access to topics from |topLevelDocument| given |document|'s [=Document/origin=].

Note: In Chrome's experimentation phase, it will additionally require a valid <a href="https://github.com/GoogleChrome/OriginTrials/blob/gh-pages/explainer.md">Origin Trial</a> token to exist in |document|.

Expand All @@ -460,8 +465,8 @@ spec: html; urlPrefix: https://www.rfc-editor.org/rfc/
1. Run the following steps [=in parallel=]:
1. Let |topics| be the result of running the [=calculate the topics for caller=] algorithm, with |topicsCallerContext| as input.
1. If <var ignore=''>options</var>["{{BrowsingTopicsOptions/skipObservation}}"] is false:
1. Run the [=collect page topics calculation input data=] algorithm with |document| as input.
1. Run the [=collect topics caller origin=] algorithm with |document| and |topicsCallerContext|'s [=topics caller context/caller origin=] as input.
1. Run the [=collect page topics calculation input data=] algorithm with |topLevelDocument| as input.
1. Run the [=collect topics caller domain=] algorithm with |topLevelDocument| and |topicsCallerContext|'s [=topics caller context/caller domain=] as input.
1. [=Queue a global task=] on the [=browsing topics task source=] given |document|'s [=relevant global object=] to perform the following steps:
1. [=Resolve=] |promise| with |topics|.
1. Return |promise|.
Expand Down Expand Up @@ -532,7 +537,7 @@ spec: html; urlPrefix: https://www.rfc-editor.org/rfc/
1. If |request|'s [=request/send browsing topics header boolean=] is not true, then return.
1. [=header list/Delete=] [:Sec-Browsing-Topics:] from |request|'s [=header list=].
<p class="note">
The topics a request is allowed to see can change within its redirect chain. For example, different caller origins may receive different topics, as the callers can only get the topics about the sites they were on. Besides, regardless of cross-origin-ness, the timestamp can also affect the candidate epochs where the topics are derived from, thus resulting in different topics across redirects.
The topics a request is allowed to see can change within its redirect chain. For example, different caller domains may receive different topics, as the callers can only get the topics about the sites they were on. The timestamp can also affect the candidate epochs where the topics are derived from, thus resulting in different topics across redirects.
</p>
1. Let |initiatorWindow| be |request|'s [=request/window=].
1. Let |requestOrigin| be |request|'s [=request/URL=]'s [=url/origin=].
Expand All @@ -543,14 +548,15 @@ spec: html; urlPrefix: https://www.rfc-editor.org/rfc/
1. Run the <a href="https://www.w3.org/TR/permissions-policy-1/#algo-should-request-be-allowed-to-use-feature">Should request be allowed to use feature?</a> algorithm with <var ignore=''>feature</var> set to |f| and <var ignore=''>request</var> set to |request|. If the algorithm returns false, then return.

Note: the above algorithm should include the <a href="https://github.com/w3c/webappsec-permissions-policy/pull/499">pending update</a>, i.e. the |request| should be considered to contain the equivalent opt-in flags for both "browsing-topic" and the "interest-cohort" feature.
1. Let |topLevelDocument| be |initiatorWindow|'s [=environment settings object/global object=]'s [=Window/navigable=]'s [=navigable/top-level traversable=]'s [=navigable/active document=].
1. Let |topicsCallerContext| be a [=topics caller context=] with default initial field values.
1. Set |topicsCallerContext|'s [=topics caller context/caller origin=] to |requestOrigin|.
1. Set |topicsCallerContext|'s [=topics caller context/top level context domain=] to the result of running |initiatorWindow|'s [=environment settings object/global object=]'s [=Window/navigable=]'s [=navigable/top-level traversable=]'s [=navigable/active document=]'s {{Document/domain}} getter steps.
1. Set |topicsCallerContext|'s [=topics caller context/caller domain=] to |requestOrigin|'s [=origin/host=]'s [=host/registrable domain=].
1. Set |topicsCallerContext|'s [=topics caller context/top level context domain=] to |topLevelDocument|'s [=Document/origin=]'s [=origin/host=]'s [=host/registrable domain=].
1. Let |unsafeMoment| be the [=wall clock=]'s [=wall clock/unsafe current time=].
1. Let |moment| be the result of running [=coarsen time=] algorithm given |unsafeMoment| and [=wall clock=] as input.
1. Let |fromUnixEpochTime| be the [=duration from=] the [=Unix epoch=] to |moment|.
1. Set |topicsCallerContext|'s [=topics caller context/timestamp=] to |fromUnixEpochTime|.
1. If the user preference setting disallows the access to topics from |topicsCallerContext|'s [=topics caller context/caller origin=] or |topicsCallerContext|'s [=topics caller context/top level context domain=], then return.
1. If the user preference setting disallows the access to topics from |topLevelDocument| given |requestOrigin|, then return.
1. Let |topics| be the result of running the [=calculate the topics for caller=] algorithm, with |topicsCallerContext| as input.
1. Let |numVersionsInEpochs| be the result of running the [=get the number of distinct versions in epochs=] algorithm, with |topicsCallerContext| as input.
1. Let |versionsToTopics| be an [=ordered map=].
Expand Down Expand Up @@ -620,7 +626,7 @@ spec: html; urlPrefix: https://www.rfc-editor.org/rfc/
<code>(100);v=chrome.1:1:20, (200);v=chrome.1:1:40, (300);v=chrome.1:1:60, ();p=P</code>
</div>

Why adding paddings: servers typically have a GET request size limit e.g. 8KB, and will return an error when the limit is reached. An attacker can rely this to learn the number of topics for a different origin, and/or a small amount of information about the topics themselves (e.g whether the [=browsing topics types/topic ids=] are < 10, < 100, etc.)
Why adding paddings: servers typically have a GET request size limit e.g. 8KB, and will return an error when the limit is reached. An attacker can rely this to learn the number of topics for a different domain, and/or a small amount of information about the topics themselves (e.g whether the [=browsing topics types/topic ids=] are < 10, < 100, etc.)

The various lengths being returned (that depends on the number of distinct versions) could leak which epochs the user had disabled topics or didn't use the browser, if it coincided with the version change. But this leak is minor. The most common cases (i.e. returning same version topics, or no topics) will have the same length.
</div>
Expand All @@ -639,12 +645,12 @@ spec: html; urlPrefix: https://www.rfc-editor.org/rfc/

1. If |request|'s [=request/header list=] does not [=list/contain=] [:Sec-Browsing-Topics:] (implying the |request|'s [=request/current URL=] is not eligible for topics), then return.
1. Let |topLevelDocument| be |request|'s [=request/window=]'s [=environment settings object/global object=]'s [=Window/navigable=]'s [=navigable/top-level traversable=]'s [=navigable/active document=].
1. Let |callerOrigin| be |request|'s [=request/current URL=]'s [=url/origin=].
1. Let |callerDomain| be |request|'s [=request/current URL=]'s [=url/origin=]'s [=origin/host=]'s [=host/registrable domain=].
1. Let |list| be |response|'s [=response/header list=].
1. Let |observe| be the result of running [=get a structured field value=] algorithm given [:Observe-Browsing-Topics:], "item", and |list| as input.
1. If |observe| is true:
1. Run the [=collect page topics calculation input data=] algorithm with |topLevelDocument| as input.
1. Run the [=collect topics caller origin=] algorithm with |topLevelDocument| and |callerOrigin| as input.
1. Run the [=collect topics caller domain=] algorithm with |topLevelDocument| and |callerDomain| as input.
</div>


Expand Down

0 comments on commit 948a26b

Please sign in to comment.