Skip to content

Commit d6bf163

Browse files
committed
[SDK] Add script to update caroots.inf
CORE-16744
1 parent dea54fb commit d6bf163

File tree

1 file changed

+178
-0
lines changed

1 file changed

+178
-0
lines changed

sdk/tools/update_caroots.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
"""
2+
PROJECT: ReactOS tools
3+
LICENSE: MIT (https://spdx.org/licenses/MIT)
4+
PURPOSE: Script to update caroots.inf with the latest CA root certificates from Mozilla NSS
5+
COPYRIGHT: Copyright 2025 Mark Jansen <[email protected]>
6+
"""
7+
8+
from enum import Enum
9+
from pathlib import Path
10+
import argparse
11+
import urllib.request
12+
from datetime import datetime
13+
from dataclasses import dataclass
14+
15+
# Additional sources:
16+
# https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt
17+
# https://hg.mozilla.org/projects/nss/raw-file/tip/lib/ckfw/builtins/certdata.txt
18+
19+
20+
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
21+
CAROOTS_INF = REPO_ROOT / "boot" / "bootdata" / "caroots.inf"
22+
DEFAULT_DOWNLOAD_URL = "https://hg.mozilla.org/mozilla-central/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt"
23+
CAROOTS_HEADER = """; Auto-generated caroots.inf
24+
; Do not edit manually.
25+
; Source: {source_url}
26+
; Generated on: {date}
27+
; Number of certificates: {cert_count}
28+
; Generated by sdk/tools/update_caroots.py
29+
30+
[Version]
31+
Signature = "$Windows NT$"
32+
33+
[AddReg]
34+
35+
"""
36+
37+
CERTIFICATE_HEADER = """; "{cert_name}" ({cert_size} bytes)
38+
HKLM,"SOFTWARE\\Microsoft\\SystemCertificates\\AuthRoot\\Certificates\\{fingerprint_sha1}","Blob",0x00000001,\\
39+
0x20,0x00,0x00,0x00,\\
40+
0x01,0x00,0x00,0x00,\\
41+
{cert_size_hex},\\
42+
{data}
43+
"""
44+
45+
46+
@dataclass
47+
class Certificate:
48+
name: str
49+
fingerprint_sha1: str
50+
data: bytes
51+
52+
53+
def format_certificate(cert: Certificate) -> str:
54+
# Format the data as comma-separated hex bytes, 16 bytes per line
55+
hex_bytes = [f"0x{b:02X}" for b in cert.data]
56+
lines = []
57+
for i in range(0, len(hex_bytes), 16):
58+
line = ",".join(hex_bytes[i : i + 16])
59+
if i + 16 < len(hex_bytes):
60+
line += ",\\"
61+
lines.append(" " + line)
62+
63+
cert_size = len(cert.data)
64+
cert_size_hex = ",".join(f"0x{b:02X}" for b in cert_size.to_bytes(4, byteorder="little"))
65+
66+
return CERTIFICATE_HEADER.format(
67+
cert_name=cert.name,
68+
fingerprint_sha1=cert.fingerprint_sha1,
69+
cert_size=cert_size,
70+
cert_size_hex=cert_size_hex,
71+
data="\n".join(lines),
72+
)
73+
74+
75+
class CurrentBlock(Enum):
76+
Begin = 0
77+
Certificate = 1
78+
Trust = 2
79+
80+
81+
def parse_certificates2(certdata: str) -> list[Certificate]:
82+
cert = None
83+
result = []
84+
temp_value = None
85+
block = CurrentBlock.Begin
86+
87+
for line in certdata.splitlines():
88+
line = line.strip()
89+
if not line or line.startswith("#"):
90+
continue
91+
92+
if line == "CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST":
93+
block = CurrentBlock.Trust
94+
continue
95+
elif line == "CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE":
96+
block = CurrentBlock.Certificate
97+
continue
98+
elif line.startswith("CKA_LABEL"):
99+
_, name, _ = line.split('"')
100+
if block == CurrentBlock.Certificate:
101+
cert = Certificate(name=name, fingerprint_sha1="", data=b"")
102+
elif block == CurrentBlock.Trust:
103+
assert cert is not None, line
104+
assert name == cert.name
105+
else:
106+
pass
107+
elif line == "CKA_VALUE MULTILINE_OCTAL":
108+
assert cert is not None
109+
temp_value = []
110+
assert cert.data == b""
111+
assert cert.fingerprint_sha1 == ""
112+
elif line == "CKA_CERT_SHA1_HASH MULTILINE_OCTAL":
113+
assert cert is not None
114+
temp_value = []
115+
assert cert.data != b""
116+
assert cert.fingerprint_sha1 == ""
117+
elif temp_value is not None:
118+
assert cert is not None
119+
if line == "END":
120+
if cert.data == b"":
121+
cert.data = bytes(temp_value)
122+
else:
123+
assert cert.fingerprint_sha1 == ""
124+
cert.fingerprint_sha1 = "".join(f"{b:02X}" for b in temp_value)
125+
temp_value = None
126+
else:
127+
for number in line.split("\\"):
128+
if not number:
129+
continue
130+
temp_value.append(int(number, 8))
131+
132+
elif line.startswith("CKA_TRUST_SERVER_AUTH"):
133+
assert cert is not None
134+
if "CKT_NSS_TRUSTED_DELEGATOR" in line:
135+
print(f"Trusted cert: {cert.name} ({len(cert.data)} bytes)")
136+
result.append(cert)
137+
cert = None
138+
return result
139+
140+
141+
def main():
142+
parser = argparse.ArgumentParser(
143+
description="Update the caroots.inf file with the latest CA root certificates."
144+
)
145+
parser.add_argument(
146+
"--url",
147+
type=str,
148+
default=DEFAULT_DOWNLOAD_URL,
149+
help="URL to download the certdata.txt file from.",
150+
)
151+
args = parser.parse_args()
152+
153+
print(f"Downloading certdata.txt from {args.url}...")
154+
with urllib.request.urlopen(args.url) as response:
155+
certdata = response.read().decode("utf-8")
156+
157+
print("Parsing certdata.txt...")
158+
certificates = parse_certificates2(certdata)
159+
certificates.sort(key=lambda c: c.name.lower())
160+
print(f"Found {len(certificates)} trusted root certificates.")
161+
print(f"Updating {CAROOTS_INF}...")
162+
with CAROOTS_INF.open("w", encoding="utf-8", newline="\r\n") as f:
163+
f.write(
164+
CAROOTS_HEADER.format(
165+
source_url=args.url,
166+
date=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
167+
cert_count=len(certificates),
168+
)
169+
)
170+
for cert in certificates:
171+
f.write(format_certificate(cert))
172+
f.write("\n")
173+
174+
print("Update complete.")
175+
176+
177+
if __name__ == "__main__":
178+
main()

0 commit comments

Comments
 (0)