mirror of
https://github.com/ApfelTeeSaft/reactos.git
synced 2026-08-26 19:33:31 +00:00
[SDK] Add script to update caroots.inf
CORE-16744
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
"""
|
||||
PROJECT: ReactOS tools
|
||||
LICENSE: MIT (https://spdx.org/licenses/MIT)
|
||||
PURPOSE: Script to update caroots.inf with the latest CA root certificates from Mozilla NSS
|
||||
COPYRIGHT: Copyright 2025 Mark Jansen <[email protected]>
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass
|
||||
|
||||
# Additional sources:
|
||||
# https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt
|
||||
# https://hg.mozilla.org/projects/nss/raw-file/tip/lib/ckfw/builtins/certdata.txt
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
CAROOTS_INF = REPO_ROOT / "boot" / "bootdata" / "caroots.inf"
|
||||
DEFAULT_DOWNLOAD_URL = "https://hg.mozilla.org/mozilla-central/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt"
|
||||
CAROOTS_HEADER = """; Auto-generated caroots.inf
|
||||
; Do not edit manually.
|
||||
; Source: {source_url}
|
||||
; Generated on: {date}
|
||||
; Number of certificates: {cert_count}
|
||||
; Generated by sdk/tools/update_caroots.py
|
||||
|
||||
[Version]
|
||||
Signature = "$Windows NT$"
|
||||
|
||||
[AddReg]
|
||||
|
||||
"""
|
||||
|
||||
CERTIFICATE_HEADER = """; "{cert_name}" ({cert_size} bytes)
|
||||
HKLM,"SOFTWARE\\Microsoft\\SystemCertificates\\AuthRoot\\Certificates\\{fingerprint_sha1}","Blob",0x00000001,\\
|
||||
0x20,0x00,0x00,0x00,\\
|
||||
0x01,0x00,0x00,0x00,\\
|
||||
{cert_size_hex},\\
|
||||
{data}
|
||||
"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Certificate:
|
||||
name: str
|
||||
fingerprint_sha1: str
|
||||
data: bytes
|
||||
|
||||
|
||||
def format_certificate(cert: Certificate) -> str:
|
||||
# Format the data as comma-separated hex bytes, 16 bytes per line
|
||||
hex_bytes = [f"0x{b:02X}" for b in cert.data]
|
||||
lines = []
|
||||
for i in range(0, len(hex_bytes), 16):
|
||||
line = ",".join(hex_bytes[i : i + 16])
|
||||
if i + 16 < len(hex_bytes):
|
||||
line += ",\\"
|
||||
lines.append(" " + line)
|
||||
|
||||
cert_size = len(cert.data)
|
||||
cert_size_hex = ",".join(f"0x{b:02X}" for b in cert_size.to_bytes(4, byteorder="little"))
|
||||
|
||||
return CERTIFICATE_HEADER.format(
|
||||
cert_name=cert.name,
|
||||
fingerprint_sha1=cert.fingerprint_sha1,
|
||||
cert_size=cert_size,
|
||||
cert_size_hex=cert_size_hex,
|
||||
data="\n".join(lines),
|
||||
)
|
||||
|
||||
|
||||
class CurrentBlock(Enum):
|
||||
Begin = 0
|
||||
Certificate = 1
|
||||
Trust = 2
|
||||
|
||||
|
||||
def parse_certificates2(certdata: str) -> list[Certificate]:
|
||||
cert = None
|
||||
result = []
|
||||
temp_value = None
|
||||
block = CurrentBlock.Begin
|
||||
|
||||
for line in certdata.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
|
||||
if line == "CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST":
|
||||
block = CurrentBlock.Trust
|
||||
continue
|
||||
elif line == "CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE":
|
||||
block = CurrentBlock.Certificate
|
||||
continue
|
||||
elif line.startswith("CKA_LABEL"):
|
||||
_, name, _ = line.split('"')
|
||||
if block == CurrentBlock.Certificate:
|
||||
cert = Certificate(name=name, fingerprint_sha1="", data=b"")
|
||||
elif block == CurrentBlock.Trust:
|
||||
assert cert is not None, line
|
||||
assert name == cert.name
|
||||
else:
|
||||
pass
|
||||
elif line == "CKA_VALUE MULTILINE_OCTAL":
|
||||
assert cert is not None
|
||||
temp_value = []
|
||||
assert cert.data == b""
|
||||
assert cert.fingerprint_sha1 == ""
|
||||
elif line == "CKA_CERT_SHA1_HASH MULTILINE_OCTAL":
|
||||
assert cert is not None
|
||||
temp_value = []
|
||||
assert cert.data != b""
|
||||
assert cert.fingerprint_sha1 == ""
|
||||
elif temp_value is not None:
|
||||
assert cert is not None
|
||||
if line == "END":
|
||||
if cert.data == b"":
|
||||
cert.data = bytes(temp_value)
|
||||
else:
|
||||
assert cert.fingerprint_sha1 == ""
|
||||
cert.fingerprint_sha1 = "".join(f"{b:02X}" for b in temp_value)
|
||||
temp_value = None
|
||||
else:
|
||||
for number in line.split("\\"):
|
||||
if not number:
|
||||
continue
|
||||
temp_value.append(int(number, 8))
|
||||
|
||||
elif line.startswith("CKA_TRUST_SERVER_AUTH"):
|
||||
assert cert is not None
|
||||
if "CKT_NSS_TRUSTED_DELEGATOR" in line:
|
||||
print(f"Trusted cert: {cert.name} ({len(cert.data)} bytes)")
|
||||
result.append(cert)
|
||||
cert = None
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Update the caroots.inf file with the latest CA root certificates."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--url",
|
||||
type=str,
|
||||
default=DEFAULT_DOWNLOAD_URL,
|
||||
help="URL to download the certdata.txt file from.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f"Downloading certdata.txt from {args.url}...")
|
||||
with urllib.request.urlopen(args.url) as response:
|
||||
certdata = response.read().decode("utf-8")
|
||||
|
||||
print("Parsing certdata.txt...")
|
||||
certificates = parse_certificates2(certdata)
|
||||
certificates.sort(key=lambda c: c.name.lower())
|
||||
print(f"Found {len(certificates)} trusted root certificates.")
|
||||
print(f"Updating {CAROOTS_INF}...")
|
||||
with CAROOTS_INF.open("w", encoding="utf-8", newline="\r\n") as f:
|
||||
f.write(
|
||||
CAROOTS_HEADER.format(
|
||||
source_url=args.url,
|
||||
date=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
cert_count=len(certificates),
|
||||
)
|
||||
)
|
||||
for cert in certificates:
|
||||
f.write(format_certificate(cert))
|
||||
f.write("\n")
|
||||
|
||||
print("Update complete.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user