Verify a published bundle
You can verify a published bundle outside the browser loader, for example before mirroring it or in a build. The Python, PHP and Go SDKs (overview; not merged and not published when this page was written) implement the rules of Signing and key epochs for the checks that apply to a single fetch:
- the Ed25519 signature over the RFC 8785 canonical form of the manifest as received, without the
signaturemember: every member is signed, members are sorted by UTF-16 code units, numbers must be integers, and duplicate members, a byte-order mark, lone surrogates and a malformed signature fail closed; - the site key equals the one you expect;
- the version is not lower than the version you already trust (rollback protection: pass the highest version you accepted);
- each language file's size and SHA-256 match its signed entry.
The three implementations pass the repository's shared canonicalisation and signature vectors, per the author (not re-run here).
What the SDK verifiers do not do. They do not implement key epochs (a later key superseding state created by an earlier one), and they do not match kid to a key. They accept a manifest signed by any key in the list you pass, and enforce only the site-key check and the minimum-version rollback check that you supply. Epoch handling is left to you: if you rotate keys, keep your own record of which key last signed an accepted manifest and apply the rules on Signing and key epochs yourself. The browser loader and edge delivery do implement epochs. This was confirmed by the SDK's author (WP-54).
Python
manifest_text = urllib.request.urlopen(f"{base}/v1/{site_key}/manifest.json").read()
manifest = rl.parse_verified_manifest(manifest_text, PUBLIC_KEYS, site_key=site_key, min_version=last_seen_version)
for entry in manifest["languages"]:
assert rl.verify_language_file(entry, download(entry["file"]))
PHP
use ResilientLang\Bundle;
$manifest = Bundle::parseVerifiedManifest($manifestText, $publicKeys, $siteKey, $lastSeenVersion);
foreach ($manifest['languages'] as $entry) {
Bundle::verifyLanguageFile($entry, $downloadedBytes) or die('tampered');
}
Bundle::verifyManifest($text, $keys) is the boolean form. It needs ext-sodium.
Go
m, err := rl.ParseVerifiedManifest(manifestText, siteKey, lastSeenVersion, publicKeyB64URL) // *rl.ManifestError on failure
for _, l := range m.Languages {
if !rl.VerifyLanguageFile(l, downloaded) { /* tampered */ }
}
VerifyManifest(text, keys...) is the boolean form and Canonicalize returns the exact bytes that are signed.
Verify the text you received, not a re-serialised copy: parsing and re-encoding JSON changes the bytes and can make a valid manifest fail or a forged one pass in another tool. The rl pull command does the same checks and writes a verified copy to disk.