Serving Signed Exchanges with Web Packager

A signed exchange (SXG) lets you authenticate a resource's origin independent of how it was delivered. Web Packager provides tooling for generating and serving SXGs. The setup differs depending on whether you're testing locally or preparing for production.

Testing with a self-signed certificate

Self-signed certificates are suitable for demonstration and testing only. Browsers will flag SXGs signed with them outside of test environments, and you should not serve them to crawlers.

Prerequisites

You'll need openssl and Go installed.

Create the certificate

Generate a private key that will be saved to priv.key:

openssl ecparam -out priv.key -name prime256v1 -genkey

Next, create a certificate signing request (CSR). The CSR below is for an organization named Web Packager Demo with common name example.com. The common name must be the fully qualified domain name of the site whose content you'll package. In production this would be a domain you own; for local testing any domain works.

openssl req -new -sha256 -key priv.key -out cert.csr -subj '/O=Web Packager Demo/CN=example.com'

Now create a certificate carrying the CanSignHttpExchanges extension. This command produces cert.pem from the private key and CSR. The -extfile flag associates the certificate with the CanSignHttpExchanges extension via its object identifier (1.3.6.1.4.1.11129.2.1.22) and also declares example.com as a Subject Alternative Name.

openssl x509 -req -days 90 -in cert.csr -signkey priv.key -out cert.pem -extfile <(echo -e "1.3.6.1.4.1.11129.2.1.22 = ASN1:NULL\nsubjectAltName=DNS:example.com")

To inspect the resulting certificate:

openssl x509 -in cert.pem -noout -text

Configure the Web Packager server

Install Web Packager, then build the webpkgserver binary:

git clone https://github.com/google/webpackager.git
cd webpackager/cmd/webpkgserver
go build .

Verify the binary is on your path:

./webpkgserver --help

This should print usage information for webpkgserver. If not, check that your GOPATH is configured correctly.

Edit webpkgserver.toml

Navigate to the webpkgserver directory and copy the example configuration:

cd /path/to/cmd/webpkgserver
cp ./webpkgserver.example.toml ./webpkgserver.toml

Open webpkgserver.toml and apply these edits:

  • Change #AllowTestCert = false to AllowTestCert = true.
  • Update PEMFile = 'path/to/your.pem' to the path of your cert.pem file. Leave the line beginning with TLS.PEMFile unchanged.
  • Update KeyFile = 'priv.key' to the path of your private key. Again, the TLS.KeyFile entry is separate.
  • Change #CertURLBase = '/webpkg/cert' to CertURLBase = 'data:'. This controls where the SXG certificate is served from, which populates the cert-url parameter in the Signature header. Production deployments would use a URL like CertURLBase = 'https://mysite.com/', but for local testing a data URL inlines the certificate directly.
  • Change Domain = 'example.org' to example.com, matching the certificate you generated. webpkgserver only fetches content from the domain named here; otherwise the logs will show the error URL doesn't match the fetch targets.

If you want to enable subresource preloading, additional options are available:

  • Switch #PreloadCSS = false to PreloadCSS = true and #PreloadJS = false to PreloadJS = true to have webpkgserver insert preload directives for stylesheet and script subresources as SXGs. Alternatively, you can add Link: rel="preload" headers and <link rel="preload"> tags manually.
  • By default, existing <link rel="preload"> tags are replaced with equivalents that fetch the content as SXG. webpkgserver sets the allowed-alt-sxg and header-integrity directives automatically, so authors don't need to write them. To preserve the original non-SXG preloads, set KeepNonSXGPreloads = true. Note that this may disqualify the SXG from the Google SXG cache per the cache requirements.

Start the server and test in Chrome

Launch the server:

./webpkgserver

Successful startup produces log messages similar to:

Listening at 127.0.0.1:8080
Successfully retrieved valid OCSP.
Writing to cache in /private/tmp/webpkg

The cache directory varies by operating system. If something fails, re-check webpkgserver.toml and restart the server after any edits.

Next, launch Chrome with certificate errors for cert.pem ignored:

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--user-data-dir=/tmp/udd \
--ignore-certificate-errors-spki-list=`openssl x509 -noout -pubkey -in cert.pem | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64`

Without this flag, DevTools will report Certificate verification error: ERR_CERT_INVALID. You may need to adjust the path to Chrome and to cert.pem. A warning beneath the address bar should appear with the hash string, e.g.: You are using an unsupported command-line flag: --ignore-certificate-errors-spki-list=9uxADcgc6/ho0mJLRMBcOjfBaN21k0sOInoMchr9CMY=. If that hash is missing, the path to the certificate is wrong.

Open the DevTools Network tab and visit:

http://localhost:8080/priv/doc/https://example.com

This requests a SXG for https://example.com through the local webpkgserver instance. /priv/doc/ is the default API endpoint.

Screenshot of the DevTools Network tab showing a SXG and its certificate.

The Network tab will list three resources:

  • A signed-exchange resource: the SXG itself.
  • A cert-chain+cbor resource: SXG certificates must be in the application/cert-chain+cbor format.
  • A document resource: the content delivered via the SXG.

If these are missing, clear the browser cache and reload the URL. The Preview tab shows details on the signed exchange and its signature:

Screenshot of the Preview tab showing a SXG

Deploying SXGs with a CanSignHttpExchanges certificate

For production, signed exchanges require a CanSignHttpExchanges certificate. The setup below assumes familiarity with the concepts covered in the self-signed certificate walkthrough.

What you’ll need

  • A CanSignHttpExchanges certificate from one of the approved certificate authorities.
  • If you don’t have one yet, you can configure webpkgserver to fetch certificates automatically from your CA. The relevant settings for webpkgserver.toml are documented in the project README.
  • An edge server in front of webpkgserver is strongly recommended. Without one, you’ll need to set TLS.PEMFile and TLS.KeyFile in webpkgserver.toml. The server runs over HTTP by default, but SXG certificates must be served over HTTPS to be trusted by browsers. Enabling TLS lets webpkgserver deliver the certificate directly.

Configuration steps

  1. Build a PEM file by concatenating your site’s SXG certificate with your CA certificate. (PEM is a common container format for multiple certificates.)

  2. Copy the example configuration to a new webpkgserver.toml:

    cp ./webpkgserver.example.toml ./webpkgserver.toml
    

  3. Edit the file:

    • Set PEMFile to the full path of your combined certificate chain.
    • Set KeyFile to the corresponding private key.
    • Set Domain to your actual site domain.
    • Optionally, enable automatic certificate renewal every 90 days (45 for Google) by filling in the [SXG.ACME] section. This only works if you have a DigiCert or Google ACME account.
  4. Point your edge server at the webpkgserver instance. The server handles two request types: SXG content via the /priv/doc/ endpoint and the SXG certificate via /webpkg/cert/. Rewriting rules differ slightly between the two; see the edge server guidance in the README.

    By default, certificates are served at /webpkg/cert/$CERT_HASH, where $CERT_HASH is derived from your PEM file:

    shell openssl base64 -in cert.pem -d | openssl dgst -sha256 -binary | base64 | tr /+ _- | tr -d =