Skip to content

Commit d95aa24

Browse files
richardlauruyadorno
authored andcommitted
tools: fix root certificate updater
Determine the NSS version from actual Firefox releases, instead of attempting to parse a wiki page (which is sensitive to formatting changes and relies on the page being up to date). PR-URL: #55681 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
1 parent 3f8a5d8 commit d95aa24

1 file changed

Lines changed: 69 additions & 117 deletions

File tree

‎tools/dep_updaters/update-root-certs.mjs‎

Lines changed: 69 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -8,109 +8,78 @@ import { pipeline } from 'node:stream/promises';
88
import{fileURLToPath}from'node:url';
99
import{parseArgs}from'node:util';
1010

11-
// Constants for NSS release metadata.
12-
constkNSSVersion='version';
13-
constkNSSDate='date';
14-
constkFirefoxVersion='firefoxVersion';
15-
constkFirefoxDate='firefoxDate';
16-
1711
const__filename=fileURLToPath(import.meta.url);
18-
constnow=newDate();
19-
20-
constformatDate=(d)=>{
21-
constiso=d.toISOString();
22-
returniso.substring(0,iso.indexOf('T'));
23-
};
2412

2513
constgetCertdataURL=(version)=>{
2614
consttag=`NSS_${version.replaceAll('.','_')}_RTM`;
27-
constcertdataURL=`https://hg.mozilla.org/projects/nss/raw-file/${tag}/lib/ckfw/builtins/certdata.txt`;
15+
constcertdataURL=`https://raw.githubusercontent.com/nss-dev/nss/refs/tags/${tag}/lib/ckfw/builtins/certdata.txt`;
2816
returncertdataURL;
2917
};
3018

31-
constnormalizeTD=(text='')=>{
32-
// Remove whitespace and any HTML tags.
33-
returntext?.trim().replace(/<.*?>/g,'');
34-
};
35-
constgetReleases=(text)=>{
36-
constreleases=[];
37-
consttableRE=/<table[^>]+>([\S\s]*?)<\/table>/g;
38-
consttableRowRE=/<tr?[^>]*>([\S\s]*?)<\/tr>/g;
39-
consttableHeaderRE=/<th?[^>]*>([\S\s]*?)<\/th>/g;
40-
consttableDataRE=/<td?[^>]*>([\S\s]*?)<\/td>/g;
41-
for(consttableoftext.matchAll(tableRE)){
42-
constcolumns={};
43-
constmatches=table[1].matchAll(tableRowRE);
44-
// First row has the table header.
45-
letrow=matches.next();
46-
if(row.done){
47-
continue;
48-
}
49-
constheaders=Array.from(row.value[1].matchAll(tableHeaderRE),(m)=>m[1]);
50-
if(headers.length>0){
51-
for(leti=0;i<headers.length;i++){
52-
if(/NSSversion/i.test(headers[i])){
53-
columns[kNSSVersion]=i;
54-
}elseif(/Release.*frombranch/i.test(headers[i])){
55-
columns[kNSSDate]=i;
56-
}elseif(/Firefoxversion/i.test(headers[i])){
57-
columns[kFirefoxVersion]=i;
58-
}elseif(/Firefoxreleasedate/i.test(headers[i])){
59-
columns[kFirefoxDate]=i;
60-
}
61-
}
62-
}
63-
// Filter out "NSS Certificate bugs" table.
64-
if(columns[kNSSDate]===undefined){
65-
continue;
66-
}
67-
// Scrape releases.
68-
row=matches.next();
69-
while(!row.done){
70-
constcells=Array.from(row.value[1].matchAll(tableDataRE),(m)=>m[1]);
71-
constrelease={};
72-
release[kNSSVersion]=normalizeTD(cells[columns[kNSSVersion]]);
73-
release[kNSSDate]=newDate(normalizeTD(cells[columns[kNSSDate]]));
74-
release[kFirefoxVersion]=normalizeTD(cells[columns[kFirefoxVersion]]);
75-
release[kFirefoxDate]=newDate(normalizeTD(cells[columns[kFirefoxDate]]));
76-
releases.push(release);
77-
row=matches.next();
78-
}
19+
constgetFirefoxReleases=async(everything=false)=>{
20+
constreleaseDataURL=`https://nucleus.mozilla.org/rna/all-releases.json${everything ? '?all=true' : ''}`;
21+
if(values.verbose){
22+
console.log(`Fetching Firefox release data from ${releaseDataURL}.`);
23+
}
24+
constreleaseData=awaitfetch(releaseDataURL);
25+
if(!releaseData.ok){
26+
console.error(`Failed to fetch ${releaseDataURL}: ${releaseData.status}: ${releaseData.statusText}.`);
27+
process.exit(-1);
7928
}
80-
returnreleases;
29+
return(awaitreleaseData.json()).filter((release)=>{
30+
// We're only interested in public releases of Firefox.
31+
return(release.product==='Firefox'&&release.channel==='Release'&&release.is_public===true);
32+
}).sort((a,b)=>{
33+
// Sort results by release date.
34+
returnnewDate(b.release_date)-newDate(a.release_date);
35+
});
8136
};
8237

83-
constgetLatestVersion=async(releases)=>{
84-
constarrayNumberSortDescending=(x,y,i)=>{
85-
if(x[i]===undefined&&y[i]===undefined){
86-
return0;
87-
}elseif(x[i]===y[i]){
88-
returnarrayNumberSortDescending(x,y,i+1);
89-
}
90-
return(y[i]??0)-(x[i]??0);
91-
};
92-
constextractVersion=(t)=>{
93-
returnt[kNSSVersion].split('.').map((n)=>parseInt(n));
94-
};
95-
constreleaseSorter=(x,y)=>{
96-
returnarrayNumberSortDescending(extractVersion(x),extractVersion(y),0);
97-
};
98-
// Return the most recent certadata.txt that exists on the server.
99-
constsortedReleases=releases.sort(releaseSorter).filter(pastRelease);
100-
for(constcandidateofsortedReleases){
101-
constcandidateURL=getCertdataURL(candidate[kNSSVersion]);
102-
if(values.verbose){
103-
console.log(`Trying ${candidateURL}`);
38+
constgetFirefoxRelease=async(version)=>{
39+
letreleases=awaitgetFirefoxReleases();
40+
letfound;
41+
if(version===undefined){
42+
// No version specified. Find the most recent.
43+
if(releases.length>0){
44+
found=releases[0];
45+
}else{
46+
if(values.verbose){
47+
console.log('Unable to find release data for Firefox. Searching full release data.');
48+
}
49+
releases=awaitgetFirefoxReleases(true);
50+
found=releases[0];
10451
}
105-
constresponse=awaitfetch(candidateURL,{method: 'HEAD'});
106-
if(response.ok){
107-
returncandidate[kNSSVersion];
52+
}else{
53+
// Search for the specified release.
54+
found=releases.find((release)=>release.version===version);
55+
if(found===undefined){
56+
if(values.verbose){
57+
console.log(`Unable to find release data for Firefox ${version}. Searching full release data.`);
58+
}
59+
releases=awaitgetFirefoxReleases(true);
60+
found=releases.find((release)=>release.version===version);
10861
}
10962
}
63+
returnfound;
11064
};
11165

112-
constpastRelease=(r)=>{
113-
returnr[kNSSDate]<now;
66+
constgetNSSVersion=async(release)=>{
67+
constlatestFirefox=release.version;
68+
constfirefoxTag=`FIREFOX_${latestFirefox.replace('.','_')}_RELEASE`;
69+
consttagInfoURL=`https://hg.mozilla.org/releases/mozilla-release/raw-file/${firefoxTag}/security/nss/TAG-INFO`;
70+
if(values.verbose){
71+
console.log(`Fetching NSS tag from ${tagInfoURL}.`);
72+
}
73+
consttagInfo=awaitfetch(tagInfoURL);
74+
if(!tagInfo.ok){
75+
console.error(`Failed to fetch ${tagInfoURL}: ${tagInfo.status}: ${tagInfo.statusText}`);
76+
}
77+
consttag=awaittagInfo.text();
78+
if(values.verbose){
79+
console.log(`Found tag ${tag}.`);
80+
}
81+
// Tag will be of form `NSS_x_y_RTM`. Convert to `x.y`.
82+
returntag.split('_').slice(1,-1).join('.');
11483
};
11584

11685
constoptions={
@@ -135,9 +104,9 @@ const {
135104
});
136105

137106
if(values.help){
138-
console.log(`Usage: ${basename(__filename)} [OPTION]... [VERSION]...`);
107+
console.log(`Usage: ${basename(__filename)} [OPTION]... [RELEASE]...`);
139108
console.log();
140-
console.log('Updates certdata.txt to NSS VERSION (most recent release by default).');
109+
console.log('Updates certdata.txt to NSS version contained in Firefox RELEASE (default: most recent release).');
141110
console.log('');
142111
console.log(' -f, --file=FILE writes a commit message reflecting the change to the');
143112
console.log(' specified FILE');
@@ -146,29 +115,11 @@ if (values.help) {
146115
process.exit(0);
147116
}
148117

149-
constscheduleURL='https://wiki.mozilla.org/NSS:Release_Versions';
150-
if(values.verbose){
151-
console.log(`Fetching NSS release schedule from ${scheduleURL}`);
152-
}
153-
constschedule=awaitfetch(scheduleURL);
154-
if(!schedule.ok){
155-
console.error(`Failed to fetch ${scheduleURL}: ${schedule.status}: ${schedule.statusText}`);
156-
process.exit(-1);
157-
}
158-
constscheduleText=awaitschedule.text();
159-
constnssReleases=getReleases(scheduleText);
160-
118+
constfirefoxRelease=awaitgetFirefoxRelease(positionals[0]);
161119
// Retrieve metadata for the NSS release being updated to.
162-
constversion=positionals[0]??awaitgetLatestVersion(nssReleases);
163-
constrelease=nssReleases.find((r)=>{
164-
returnnewRegExp(`^${version.replace('.','\\.')}\\b`).test(r[kNSSVersion]);
165-
});
166-
if(!pastRelease(release)){
167-
console.warn(`Warning: NSS ${version} is not due to be released until ${formatDate(release[kNSSDate])}`);
168-
}
120+
constversion=awaitgetNSSVersion(firefoxRelease);
169121
if(values.verbose){
170-
console.log('Found NSS version:');
171-
console.log(release);
122+
console.log(`Updating to NSS version ${version}`);
172123
}
173124

174125
// Fetch certdata.txt and overwrite the local copy.
@@ -213,14 +164,15 @@ const added = [ ...diff.matchAll(certsAddedRE) ].map((m) => m[1]);
213164
constremoved=[ ...diff.matchAll(certsRemovedRE)].map((m)=>m[1]);
214165

215166
constcommitMsg=[
216-
`crypto: update root certificates to NSS ${release[kNSSVersion]}`,
167+
`crypto: update root certificates to NSS ${version}`,
217168
'',
218-
`This is the certdata.txt[0] from NSS ${release[kNSSVersion]}, released on ${formatDate(release[kNSSDate])}.`,
219-
'',
220-
`This is the version of NSS that ${release[kFirefoxDate]<now ? 'shipped' : 'will ship'} in Firefox ${release[kFirefoxVersion]} on`,
221-
`${formatDate(release[kFirefoxDate])}.`,
169+
`This is the certdata.txt[0] from NSS ${version}.`,
222170
'',
223171
];
172+
if(firefoxRelease){
173+
commitMsg.push(`This is the version of NSS that shipped in Firefox ${firefoxRelease.version} on ${firefoxRelease.release_date}.`);
174+
commitMsg.push('');
175+
}
224176
if(added.length>0){
225177
commitMsg.push('Certificates added:');
226178
commitMsg.push(...added.map((cert)=>`- ${cert}`));
@@ -234,7 +186,7 @@ if (removed.length > 0) {
234186
commitMsg.push(`[0] ${certdataURL}`);
235187
constdelimiter=randomUUID();
236188
constproperties=[
237-
`NEW_VERSION=${release[kNSSVersion]}`,
189+
`NEW_VERSION=${version}`,
238190
`COMMIT_MSG<<${delimiter}`,
239191
...commitMsg,
240192
delimiter,

0 commit comments

Comments
 (0)