Chrome UA文字列凍結とUA-CHについて

はじめまして。
CyberAgent Ameba事業本部 広告プロダクトグループの星と申します。
主に広告計測やPrivacy保護関連を担当しております。

こちらは、CyberAgentメディア管轄の広告プロダクト横軸組織、通称PTAの[アドベントカレンダー2020](https://adventar.org/calendars/5741)の10日目の投稿になりまして、
最近調査していた「Chrome UA文字列凍結とUA-CHについて」についてまとめさせていただきます。
間違いなどありましたらご指摘いただけると助かります。

[PTAの活動についてはこちら](https://qiita.com/wataru420/items/8968f1127d0af00aea99)

# UA文字列の凍結とUA-CHについて
まずは以下を参考に概要を

- https://groups.google.com/a/chromium.org/g/blink-dev/c/-2JIRNMWJ7s/m/yHe4tQNLCgAJ
- https://chromestatus.com/feature/5704553745874944

## UA文字列の凍結と統一
以下をFreeze・統一したい（削除はしない）

- HTTP RequestのUser-Agent文字列
- navigator.userAgent

UA文字列の例で言うと、以下の様な文字列になるかと思います。
**Android Pixel4a Chrome**
```Mozilla/5.0 (Linux; Android 11; Pixel 4a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.110 Mobile Safari/537.36```

### なぜやるのか？

- Privacy問題(PassiveFingerPrinting Information多い)
- User-Agent文字列に互換性を目的とした虚偽の情報が多く含まれている。
- マイノリティーなブラウザではUser-Agentを偽る状況が起こっており、サイトが正しく表示されないこともある。
- User-Agent文字列を凍結し、User-Agent Client Hints(UA-CH)に置き換える

## UA-CH(User-Agent Client Hints)の主な利点
UA-CHの内容自体は後述しますが、以下が主な利点として挙げられています。

- Secureな接続でサーバが要求した時のみに情報を提供する
    - Active FingerPrintingとすることで、使用の監視ができる
- いくつかの項目に分かれているので必要な時に必要な情報だけのやりとりになる。（現状のUser-Agent文字列全体が晒されない）
- 専用のフィールドを介するので、誤操作・互換性の問題が発生しにくくなる
- 現在のUser-Agent文字列の大量のレガシー情報を処分できる
    - Mozilla/5.0
    - like Gecko
    - like KHTML
    - etc...

## UA文字列の凍結時期は？
[こちら](https://groups.google.com/a/chromium.org/d/msg/blink-dev/-2JIRNMWJ7s/u-YzXjZ8BAAJ)を見るに、コロナウィルスの影響もあり、
「少なくとも2021年まで」延期されたようです。

ちょっと投稿が古いのですが、私の方ではその後のリリース時期のアップデートは見つけられませんでした。

## 凍結後も重要なバージョンは更新
- User-Agent文字列の凍結後も「significant version」は引き続き更新されるとのこと。
- 具体的な更新例などは示されていない。

参考 : https://groups.google.com/a/chromium.org/g/blink-dev/c/-2JIRNMWJ7s/m/0hu2ATesAQAJ

# UA-CHについて
では、UA-CH(User-Agent Client Hints)についてです。
## Chromeの状況
Chromeの状況は以下。

- Chrome v.84のリリースに含まれている
    - https://www.chromestatus.com/feature/5995832180473856

- Chrome v.85以降で段階的なroll out
    - https://www.chromium.org/updates/ua-ch
    - 現時点ではロールアウト対象の拡大は止めているようです

- こちらの記事公開時点のStableは、Chrome v.87になっています。
    - https://www.chromestatus.com/features/schedule

## UA-CH項目
<table>
  <tr>
    <th>項目</th>
    <th>内容</th>
    <th>リクエストヘッダフィールド</th>
    <th>デフォルト</th>
    <th>Sample値</th>
  </tr>
  <tr>
    <td>brand</td>
    <td>ブラウザ名</td>
    <td rowspan="2">Sec-CH-UA</td>
    <td rowspan="2">○</td>
    <td rowspan="2">"Chromium";v="86", "\"Not\\A;Brand";v="99", "Google Chrome";v="86"</td>
  </tr>
  <tr>
    <td>significant version</td>
    <td>重要バージョン</td>
  </tr>
  <tr>
    <td>full version</td>
    <td>ビルドバージョン</td>
    <td>Sec-CH-UA-Platform</td>
    <td>x</td>
    <td>"86.0.4240.80"</td>
  </tr>
  <tr>
    <td>platform brand</td>
    <td>OS名</td>
    <td>Sec-CH-UA-Platform-Version</td>
    <td>x</td>
    <td>"Android"</td>
  </tr>
  <tr>
    <td>platform version</td>
    <td>OSバージョン</td>
    <td>Sec-CH-UA-Arch</td>
    <td>x</td>
    <td>"8.0"</td>
  </tr>
  <tr>
    <td>platform architecture</td>
    <td>CPU architecture</td>
    <td>Sec-CH-UA-Arch</td>
    <td>x</td>
    <td>"x86"</td>
  </tr>
  <tr>
    <td>model</td>
    <td>デバイスモデル</td>
    <td>Sec-CH-UA-Model</td>
    <td>x</td>
    <td>"Pixel 2"</td>
  </tr>
  <tr>
    <td>mobileness</td>
    <td>モバイル判定</td>
    <td>Sec-CH-UA-Mobile</td>
    <td>○</td>
    <td>?1</td>
  </tr>
</table>

参考：

- https://github.com/WICG/ua-client-hints#a-proposal
- https://wicg.github.io/ua-client-hints/




## UA-CHの取得方法
大きく以下の2つになります

1. リクエストヘッダ
1. JavaScript API

### リクエストヘッダでの取得について

- https必須
- デフォルトで以下のフィールドが送られる
    - Sec-CH-UA
    - Sec-CH-UA-Mobile
- オプトインすることで以下のフィールドが送られる
    - Sec-CH-UA-Full-Version
    - Sec-CH-UA-Platform
    - Sec-CH-UA-Platform-Version
    - Sec-CH-UA-Arch
    - Sec-CH-UA-Model
- 高エントロピーな情報はデフォルトでは送られない。
    - [参考](https://groups.google.com/a/chromium.org/g/blink-dev/c/A4wxFpvqUfA/m/_FR1xvOuBwAJ)

### デフォルト以外の取得(オプトイン)方法
metaタグでもできるようですが、ここではレスポンスヘッダのAccept-CHでのオプトインについて。
![スクリーンショット 2020-12-03 午後7.24.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1326/774260ff-9136-68de-3378-b72617992fe0.png)

## Cross-Originでリクエストヘッダで取得
Cross-Originでリクエストヘッダでデフォルト以外のHintを取得するには
Origin側のレスポンスヘッダのFeature-Policy(Permissions-Policy)を使ってのdelegateが必要になるようです。
広告だと基本Cross-Originなので、リクエストヘッダで受け取るには配信メディア側の対応が必要になります。
仮に配信メディア側で対応してもらった場合、以下の様な形になるかと思います。
![スクリーンショット 2020-12-03 午後7.28.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1326/93cba405-5521-f72f-f9dd-ea844fb97b12.png)
### 補足
私のほうで試したところ、以下のような動きだったので参考までに。

- Origin側でオプトインしていないと、Cross-Originにdelegateできない(Feature-Policyの指定だけだとダメ)
- Origin側でオプトイン・Cross-OriginへdelegateしていればCross-Origin側でのオプトインは不要。
初回リクエストから乗ってくる

### Life-Timeとリセットタイミング
以下になるようです。

- ブラウザセッションの間
- 別のHintが指定されるまで

![スクリーンショット 2020-12-03 午後7.31.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1326/78b42064-f4b2-1d3f-6d5d-d75284181292.png)


### リクエストヘッダについて
[Structured Field Values](https://httpwg.org/http-extensions/draft-ietf-httpbis-header-structure.html)に基づく

- Http仕様では許可されているが、以前のRequestHeaderでは一般的でなかった以下の様な文字が含まれている。
    - double-quotes (")
    - equal signs (=)
    - forward-slashes (/)
    - question marks (?)
- 既存のシステムのどこかでこちらを弾いてしまう可能性がある
- 4xxが返ってくるはず
    - Parseには以下のような標準Parserを使うのを推奨
        - https://github.com/mnot/http_sfv

参考 : https://www.chromium.org/updates/ua-ch


### GREASE(Generate Random Extensions And Sustain Extensibility)
[TLS GREASE](https://tools.ietf.org/html/draft-ietf-tls-grease-04)の概念を取り込む

**brand(Sec-CH-UA)について**

- brandに複数の値を含める必要がある。値の少なくとも1つは任意の値。
- 任意の値を追加するとき、値には以下を追加する。
    - escaped double-quote
    - commas
    - semi-colons
- brandの値の順序は時間の経過と共に変化しなければならない。
- Chrome v.86例 (<font color="red">赤字</font>がGREASEにあたる部分になります。)
    - "Chromium";v="86", <font color="red">"\"Not\\A;Brand";v="99",</font> "Google Chrome";v="86"

準拠しているかの基準になり、問題がある場合、早期に気が付けるように。(4xxが返る)

参考 : https://wicg.github.io/ua-client-hints/#grease

## JavaScript APIでの取得
**navigator.userAgentData**
![スクリーンショット 2020-12-03 午後7.49.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1326/b9d4c178-942c-bc49-a827-7d1ef6f295ab.png)

**navigator.userAgentData.getHighEntropyValues**
高エントロピー情報はPromiseを介する。（ユーザに許可を求めたりが想定されるため。）
![スクリーンショット 2020-12-03 午後7.51.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1326/8447081b-3549-81d3-3f34-244ba554817b.png)

# 懸念点
ここまでざっと書いてしまいましたが、以下が懸念として挙げられるかと。

- PrivacyBudgetとの兼ね合い
    - とりあえず全部取得としたいところなのですが、以下でPrivacyBudgetで制限される旨の記載もあります。
    - https://github.com/WICG/ua-client-hints/blob/master/README.md#persistent-user-tracking
    - PrivacyBudgetについては[こちら](https://github.com/bslassey/privacy-budget)
    - なんらか取得項目を制御できる仕組みは最初から入れておきたい。
- OpenRTB上のフィールドは？
    - 以下の質問も挙がっているようですが、解決はしていなそうです。
    - https://groups.google.com/a/chromium.org/g/blink-dev/c/-2JIRNMWJ7s/m/g1AGZrpKDgAJ
    - https://groups.google.com/a/chromium.org/g/blink-dev/c/-2JIRNMWJ7s/m/sFR8F1LLDgAJ
- 広告の場合は結局JavaScriptで取得する必要があるのでは？
    - すべての配信メディアやLPにFeature-Policyの設定してもらうこと現実的なのか？
    - デフォルトのHint以外も基本必要になるはず。
    - JavaScriptのSDKで取得して広告リクエストパラメータに付与することになるのでは？

# リクエストヘッダとりあえず確認したい場合
- UA-CH有効にする
    - chrome://flags
    - 「Experimental Web Platform features」をEnabledに
- 確認サイト
    - https://user-agent-client-hints.glitch.me/


# 最後に
最後まで読んでいただきありがとうございました。

今後、ブラウザベンダ的にも法律的にもPrivacyに関しての規制がより厳しくなっていくことが予想されます。
ユーザのPrivacyをきちんと守れる状態で、より良い広告配信を実現できるようにしていければと考えております。

また一緒に働いていただける方、絶賛募集中ですので興味のある方は[こちら](https://hrmos.co/pages/cyberagent-group/jobs/0000004)から是非！