Webページを丸ごとパッケージングする Web Packagingとは

2017001追記
IETF99にてユースケースについてまずまとめるべき気というフィーロバックが有り、それをうけて「Use Cases and Requirements for Web Packages」が提出されています


Webページを丸ごとパッケージングする、Web Packagingの仕様がIETFで提案されています

オフラインやローカル環境で共有出来るようになっており、主な特徴は

  • 証明書及び署名がつけられるため、そのパッケージの真正性が確認できる
  • HTTPリクエスト/HTTPレスポンスも含まれている(HPACKを利用)
  • サブパッケージが利用でき、複数のオリジンをパッケージング可能
  • データはCBORで表現される (RFC 7049 - Concise Binary Object Representation (CBOR))
  • Index化されており、オフセットを用いて各リソースにランダムアクセス可能

以上の点で、ZIPで固めて保存するのとはまったく違うことがわかるかと思います。

ユースケース

幾つかユースケースがありますが、主なものは以下のとおりです

  • ローカル環境での共有。SDカードでの共有から、Physical Webと言ったその場でのBluetoothを用いた配信など
  • Webのスナップショット保存。そのページをスナップショットとして保存する(署名無し)
  • CDNでの配布。CDNや別の場所から再配布することが可能。この場合は署名をつけての配布が可能となる

提案仕様と経緯

6月30日に、GoogleのJeffrey Yasskin氏より「Web Packaging」という提案がIETFにて出されました。おそらく議論はDispatch WGで行われ、今月実施されるIETF99
IETF 99
でもオフラインでの議論があるかもしれません。

もともと、Webページをパッケージングする仕様は、W3Ctag「Packaging on the Web」というものがありましたが、そこから署名などの機能を追加し整理した「Web Packaging」という仕様がW3CのWICGで議論されておりました。

このW3C WICGの仕様は、データフォマットが要でありIETFで標準化していくのが適切ではないかとIETFに持ち込まれた形になります。

フォーマット

webpackageのフォーマットは以下の通りである

webpackage = [
  magic1: h'F0 9F 8C 90 F0 9F 93 A6',  ; 🌐📦 in UTF-8.
  section-offsets: { * (($section-name .within tstr) => offset) },
  sections: ({ * $$section }) .within ({ * $section-name => any }),
  length: uint,                        ; Total number of bytes in the package.
  magic2: h'F0 9F 8C 90 F0 9F 93 A6',  ; 🌐📦 in UTF-8.
]
  • magic: 先頭と最後に挿入されるMagicコード。意味はない
  • section-offsets: 各セクションへのオフセット値が与えられる。必須ではない
  • sections: ここにManifestや実際のコンテンツが格納される(manifest, indexed-content)
  • length: データ長

manifest及び、indexed-contentは以下で説明します。

manifest

sectionsに格納されるmanifestデータ。必須ではない。

日付、オリジン名、証明書、署名が格納される。また、アレば別オリジンのパッケージをサブパッケージとして読み込むことが出来る。

具体的には以下のパラメータです

  • metadata: 日付、及びオリジンが記述される。また、あればサブパッケージの指定
  • resource-hashes: 各リソースのハッシュ値
  • signatures: 署名
  • certificates: 証明書

    "manifest": {
      "manifest": {
        "metadata": {
          "date": 1(1494583200),
          "origin": 32("https://example.com")
        },
        "resource-hashes": {
          "sha384": [
            h'3C3A03F7C3FC99494F6AAA25C3D11DA3C0D7097ABBF5A9476FB64741A769984E8B6801E71BB085E25D7134287B99BAAB',
            ...
          ]
        }
      },
      "signatures": [
        {
          "keyIndex": 0,
          "signature": h'3044022015B1C8D46E4C6588F73D9D894D05377F382C4BC56E7CDE41ACEC1D81BF1EBF7E02204B812DACD001E0FD4AF968CF28EC6152299483D6D14D5DBE23FC1284ABB7A359'
        }
      ],
      "certificates": [
        DER(
          Certificate:
              ...
              Signature Algorithm: ecdsa-with-SHA256
                  Issuer: C=US, O=Honest Achmed's, CN=Honest Achmed's Test Intermediate CA
                      Public Key Algorithm: id-ecPublicKey
                          Public-Key: (256 bit)
                          pub:
                              ...
        ),
        DER(
          Certificate:
              ...
        )
      ]
    },
indexed-content

sectionsに格納される、indexed-content。ここにコンテンツの中身が格納される。
各リソースに対するHTTPリクエスト及びHTTPレスポンスが合わせて格納されており、ヘッダに関してはそれぞれHPACKでエンコードされている。

    "indexed-content": [
      [
        [ hpack({
            :method: GET
            :scheme: https
            :authority: example.com
            :path: /index.html
          }), 1]
        [ hpack({
            :method: GET
            :scheme: https
            :authority: example.com
            :path: /otherPage.html
          }), 121],
        [ hpack({
            :method: GET
            :scheme: https
            :authority: example.com
            :path: /images/world.png
          }), 243]
        ],
      ],
      [
        [ hpack({
            :status: 200
            content-type: text/html
            date: Wed, 15 Nov 2016 06:25:24 GMT
            expires: Thu, 01 Jan 2017 16:00:00 GMT
          }),
          '<body>\n  <a href=\"otherPage.html\">Other page</a>\n</body>\n'
        ]
        [ hpack({
            :status: 200
            content-type: text/html
            date: Wed, 15 Nov 2016 06:25:24 GMT
            expires: Thu, 01 Jan 2017 16:00:00 GMT
          }),
          '<body>\n  Hello World! <img src=\"images/world.png\">\n</body>\n'
        ],
        [ hpack({
            :status: 200
            content-type: image/png
            date: Wed, 15 Nov 2016 06:25:24 GMT
            expires: Thu, 01 Jan 2017 16:00:00 GMT
          }),
          '... binary png image ...'
        ]
      ]
    ]

セキュリティについて

オフラインの利用を想定しているため、証明書の失効確認については難しいところがあります。Webパッケージの利用者は正当性を確認する際は、オンライン時はOCSPを利用して失効確認が可能ですが、オフラインのときはブラウザによって判断されることになるでしょう(1週間だけ有効など)。

そこの部分については今後の議論になるかと思われます。