Meta: Generate HSTS preload data from Chromium's static list
Download transport_security_state_static.json on configure and emit a sorted flat array of (name, include_subdomains) pairs. The generator keeps only force-https entries, drops IP literals, and validates names against [a-z0-9.-] so the emitted C++ string literals need no escaping. Matches Firefox's HSTS preload source: https://searchfox.org/firefox-main/rev/ca47ec6bb1f3e46a384cd1ebb11c5996cceef90f/taskcluster/docker/periodic-updates/scripts/getHSTSPreloadList.js
This commit is contained in:
parent
9be221abff
commit
d4d9fc12f7
3 changed files with 202 additions and 0 deletions
|
|
@ -718,6 +718,12 @@
|
|||
"dest": "Caches/PublicSuffix/",
|
||||
"sha256": "e79e372bcc6fcdb51f7a31e3c0c504530838432669af2ac544d2491de0a86030"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://raw.githubusercontent.com/chromium/chromium/aa04f175415addb04bb78936b0d8b973fbd8ea61/net/http/transport_security_state_static.json",
|
||||
"dest": "Caches/HSTSPreload/",
|
||||
"sha256": "4ca70f051571a198246bc337bd357f759fb8f9fdeddbe66b65960997540ee163"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"commands": [
|
||||
|
|
|
|||
24
Meta/CMake/hsts_preload.cmake
Normal file
24
Meta/CMake/hsts_preload.cmake
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
include(${CMAKE_CURRENT_LIST_DIR}/utils.cmake)
|
||||
|
||||
set(HSTS_PRELOAD_PATH "${LADYBIRD_CACHE_DIR}/HSTSPreload" CACHE PATH "Download location for HSTS preload files")
|
||||
set(HSTS_PRELOAD_DATA_URL "https://raw.githubusercontent.com/chromium/chromium/main/net/http/transport_security_state_static.json")
|
||||
set(HSTS_PRELOAD_DATA_PATH "${HSTS_PRELOAD_PATH}/transport_security_state_static.json")
|
||||
set(HSTS_PRELOAD_DATA_HEADER HSTSPreloadData.h)
|
||||
set(HSTS_PRELOAD_DATA_IMPLEMENTATION HSTSPreloadData.cpp)
|
||||
if (ENABLE_NETWORK_DOWNLOADS)
|
||||
download_file("${HSTS_PRELOAD_DATA_URL}" "${HSTS_PRELOAD_DATA_PATH}")
|
||||
else()
|
||||
message(STATUS "Skipping download of ${HSTS_PRELOAD_DATA_URL}, expecting it to be in ${HSTS_PRELOAD_DATA_PATH}")
|
||||
endif()
|
||||
invoke_py_generator(
|
||||
"HSTSPreloadData"
|
||||
"generate_hsts_preload_data.py"
|
||||
"${HSTS_PRELOAD_PATH}/"
|
||||
"${HSTS_PRELOAD_DATA_HEADER}"
|
||||
"${HSTS_PRELOAD_DATA_IMPLEMENTATION}"
|
||||
arguments -p "${HSTS_PRELOAD_DATA_PATH}"
|
||||
)
|
||||
set(HSTS_PRELOAD_SOURCES
|
||||
${HSTS_PRELOAD_DATA_HEADER}
|
||||
${HSTS_PRELOAD_DATA_IMPLEMENTATION}
|
||||
)
|
||||
172
Meta/Generators/generate_hsts_preload_data.py
Normal file
172
Meta/Generators/generate_hsts_preload_data.py
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2026-present, the Ladybird developers.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
import argparse
|
||||
import ipaddress
|
||||
import json
|
||||
import re
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
NAME_RE = re.compile(r"[a-z0-9.-]+")
|
||||
|
||||
|
||||
def parse_entries(input_path: Path) -> list[tuple[str, bool]]:
|
||||
text = input_path.read_text(encoding="utf-8")
|
||||
|
||||
# Chromium's transport_security_state_static.json uses // line comments, which are not legal JSON.
|
||||
text = re.sub(r"(?m)^\s*//.*$", "", text)
|
||||
|
||||
data = json.loads(text)
|
||||
raw_entries = data["entries"]
|
||||
|
||||
result: list[tuple[str, bool]] = []
|
||||
for entry in raw_entries:
|
||||
if entry.get("mode") != "force-https":
|
||||
continue
|
||||
|
||||
name = entry["name"]
|
||||
|
||||
try:
|
||||
ipaddress.ip_address(name)
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
name = name.lower()
|
||||
if not NAME_RE.fullmatch(name):
|
||||
raise ValueError(
|
||||
f"HSTS preload entry name {name!r} contains characters outside [a-z0-9.-]; "
|
||||
f"refusing to emit it as a C++ string literal without an explicit policy."
|
||||
)
|
||||
|
||||
include_subdomains = bool(entry.get("include_subdomains", False))
|
||||
result.append((name, include_subdomains))
|
||||
|
||||
result.sort(key=lambda pair: pair[0])
|
||||
|
||||
seen: set[str] = set()
|
||||
for name, _ in result:
|
||||
if name in seen:
|
||||
raise ValueError(f"Duplicate HSTS preload entry: {name!r}")
|
||||
seen.add(name)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_header_file(output_path: Path) -> None:
|
||||
content = """#pragma once
|
||||
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/StringView.h>
|
||||
|
||||
namespace HTTP {
|
||||
|
||||
struct HSTSPreloadEntry {
|
||||
StringView name;
|
||||
bool include_subdomains;
|
||||
};
|
||||
|
||||
class HSTSPreloadData {
|
||||
AK_MAKE_NONCOPYABLE(HSTSPreloadData);
|
||||
AK_MAKE_NONMOVABLE(HSTSPreloadData);
|
||||
|
||||
public:
|
||||
static HSTSPreloadData const& the();
|
||||
|
||||
Optional<HSTSPreloadEntry> find_exact(StringView lowercased_domain) const;
|
||||
bool is_known_preloaded_hsts_host(StringView domain) const;
|
||||
|
||||
private:
|
||||
HSTSPreloadData() = default;
|
||||
};
|
||||
|
||||
}
|
||||
"""
|
||||
output_path.write_text(content, encoding="utf-8")
|
||||
|
||||
|
||||
def generate_implementation_file(entries: list[tuple[str, bool]], output_path: Path) -> None:
|
||||
lines: list[str] = []
|
||||
lines.append("#include <AK/Array.h>")
|
||||
lines.append("#include <AK/BinarySearch.h>")
|
||||
lines.append("#include <AK/StringView.h>")
|
||||
lines.append("#include <LibHTTP/HSTSPreloadData.h>")
|
||||
lines.append("")
|
||||
lines.append("namespace HTTP {")
|
||||
lines.append("")
|
||||
lines.append(f"static constexpr Array<HSTSPreloadEntry, {len(entries)}> s_hsts_preload_entries {{ {{")
|
||||
for name, include_subdomains in entries:
|
||||
flag = "true" if include_subdomains else "false"
|
||||
lines.append(f' HSTSPreloadEntry {{ "{name}"sv, {flag} }},')
|
||||
lines.append("} };")
|
||||
lines.append("")
|
||||
lines.append("HSTSPreloadData const& HSTSPreloadData::the()")
|
||||
lines.append("{")
|
||||
lines.append(" static HSTSPreloadData s_the;")
|
||||
lines.append(" return s_the;")
|
||||
lines.append("}")
|
||||
lines.append("")
|
||||
lines.append("Optional<HSTSPreloadEntry> HSTSPreloadData::find_exact(StringView needle) const")
|
||||
lines.append("{")
|
||||
lines.append(" auto* hit = binary_search(")
|
||||
lines.append(" s_hsts_preload_entries,")
|
||||
lines.append(" needle,")
|
||||
lines.append(" nullptr,")
|
||||
lines.append(" [](StringView lhs, HSTSPreloadEntry const& rhs) {")
|
||||
lines.append(" return lhs.compare(rhs.name);")
|
||||
lines.append(" });")
|
||||
lines.append(" if (!hit)")
|
||||
lines.append(" return {};")
|
||||
lines.append(" return *hit;")
|
||||
lines.append("}")
|
||||
lines.append("")
|
||||
lines.append("// https://www.rfc-editor.org/rfc/rfc6797#section-8.2")
|
||||
lines.append("bool HSTSPreloadData::is_known_preloaded_hsts_host(StringView domain) const")
|
||||
lines.append("{")
|
||||
lines.append(" if (find_exact(domain).has_value())")
|
||||
lines.append(" return true;")
|
||||
lines.append(" auto remaining = domain;")
|
||||
lines.append(" while (true) {")
|
||||
lines.append(" auto dot = remaining.find('.');")
|
||||
lines.append(" if (!dot.has_value())")
|
||||
lines.append(" break;")
|
||||
lines.append(" remaining = remaining.substring_view(*dot + 1);")
|
||||
lines.append(" if (auto entry = find_exact(remaining); entry.has_value() && entry->include_subdomains)")
|
||||
lines.append(" return true;")
|
||||
lines.append(" }")
|
||||
lines.append(" return false;")
|
||||
lines.append("}")
|
||||
lines.append("")
|
||||
lines.append("}")
|
||||
lines.append("")
|
||||
|
||||
output_path.write_text("\n".join(lines), encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Generate HSTS preload data files", add_help=False)
|
||||
parser.add_argument("--help", action="help", help="Show this help message and exit")
|
||||
parser.add_argument("-h", "--generated-header-path", required=True, help="Path to the header file to generate")
|
||||
parser.add_argument(
|
||||
"-c", "--generated-implementation-path", required=True, help="Path to the implementation file to generate"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--hsts-preload-list-path",
|
||||
required=True,
|
||||
help="Path to Chromium's transport_security_state_static.json",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
entries = parse_entries(Path(args.hsts_preload_list_path))
|
||||
generate_header_file(Path(args.generated_header_path))
|
||||
generate_implementation_file(entries, Path(args.generated_implementation_path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in a new issue