mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
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: https://github.com/nodejs/node/pull/55681 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
This commit is contained in:
parent
8dd0819ff3
commit
794cb51112
@ -8,109 +8,78 @@ import { pipeline } from 'node:stream/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { parseArgs } from 'node:util';
|
||||
|
||||
// Constants for NSS release metadata.
|
||||
const kNSSVersion = 'version';
|
||||
const kNSSDate = 'date';
|
||||
const kFirefoxVersion = 'firefoxVersion';
|
||||
const kFirefoxDate = 'firefoxDate';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const now = new Date();
|
||||
|
||||
const formatDate = (d) => {
|
||||
const iso = d.toISOString();
|
||||
return iso.substring(0, iso.indexOf('T'));
|
||||
};
|
||||
|
||||
const getCertdataURL = (version) => {
|
||||
const tag = `NSS_${version.replaceAll('.', '_')}_RTM`;
|
||||
const certdataURL = `https://hg.mozilla.org/projects/nss/raw-file/${tag}/lib/ckfw/builtins/certdata.txt`;
|
||||
const certdataURL = `https://raw.githubusercontent.com/nss-dev/nss/refs/tags/${tag}/lib/ckfw/builtins/certdata.txt`;
|
||||
return certdataURL;
|
||||
};
|
||||
|
||||
const normalizeTD = (text = '') => {
|
||||
// Remove whitespace and any HTML tags.
|
||||
return text?.trim().replace(/<.*?>/g, '');
|
||||
const getFirefoxReleases = async (everything = false) => {
|
||||
const releaseDataURL = `https://nucleus.mozilla.org/rna/all-releases.json${everything ? '?all=true' : ''}`;
|
||||
if (values.verbose) {
|
||||
console.log(`Fetching Firefox release data from ${releaseDataURL}.`);
|
||||
}
|
||||
const releaseData = await fetch(releaseDataURL);
|
||||
if (!releaseData.ok) {
|
||||
console.error(`Failed to fetch ${releaseDataURL}: ${releaseData.status}: ${releaseData.statusText}.`);
|
||||
process.exit(-1);
|
||||
}
|
||||
return (await releaseData.json()).filter((release) => {
|
||||
// We're only interested in public releases of Firefox.
|
||||
return (release.product === 'Firefox' && release.channel === 'Release' && release.is_public === true);
|
||||
}).sort((a, b) => {
|
||||
// Sort results by release date.
|
||||
return new Date(b.release_date) - new Date(a.release_date);
|
||||
});
|
||||
};
|
||||
const getReleases = (text) => {
|
||||
const releases = [];
|
||||
const tableRE = /<table [^>]+>([\S\s]*?)<\/table>/g;
|
||||
const tableRowRE = /<tr ?[^>]*>([\S\s]*?)<\/tr>/g;
|
||||
const tableHeaderRE = /<th ?[^>]*>([\S\s]*?)<\/th>/g;
|
||||
const tableDataRE = /<td ?[^>]*>([\S\s]*?)<\/td>/g;
|
||||
for (const table of text.matchAll(tableRE)) {
|
||||
const columns = {};
|
||||
const matches = table[1].matchAll(tableRowRE);
|
||||
// First row has the table header.
|
||||
let row = matches.next();
|
||||
if (row.done) {
|
||||
continue;
|
||||
}
|
||||
const headers = Array.from(row.value[1].matchAll(tableHeaderRE), (m) => m[1]);
|
||||
if (headers.length > 0) {
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
if (/NSS version/i.test(headers[i])) {
|
||||
columns[kNSSVersion] = i;
|
||||
} else if (/Release.*from branch/i.test(headers[i])) {
|
||||
columns[kNSSDate] = i;
|
||||
} else if (/Firefox version/i.test(headers[i])) {
|
||||
columns[kFirefoxVersion] = i;
|
||||
} else if (/Firefox release date/i.test(headers[i])) {
|
||||
columns[kFirefoxDate] = i;
|
||||
}
|
||||
|
||||
const getFirefoxRelease = async (version) => {
|
||||
let releases = await getFirefoxReleases();
|
||||
let found;
|
||||
if (version === undefined) {
|
||||
// No version specified. Find the most recent.
|
||||
if (releases.length > 0) {
|
||||
found = releases[0];
|
||||
} else {
|
||||
if (values.verbose) {
|
||||
console.log('Unable to find release data for Firefox. Searching full release data.');
|
||||
}
|
||||
releases = await getFirefoxReleases(true);
|
||||
found = releases[0];
|
||||
}
|
||||
// Filter out "NSS Certificate bugs" table.
|
||||
if (columns[kNSSDate] === undefined) {
|
||||
continue;
|
||||
}
|
||||
// Scrape releases.
|
||||
row = matches.next();
|
||||
while (!row.done) {
|
||||
const cells = Array.from(row.value[1].matchAll(tableDataRE), (m) => m[1]);
|
||||
const release = {};
|
||||
release[kNSSVersion] = normalizeTD(cells[columns[kNSSVersion]]);
|
||||
release[kNSSDate] = new Date(normalizeTD(cells[columns[kNSSDate]]));
|
||||
release[kFirefoxVersion] = normalizeTD(cells[columns[kFirefoxVersion]]);
|
||||
release[kFirefoxDate] = new Date(normalizeTD(cells[columns[kFirefoxDate]]));
|
||||
releases.push(release);
|
||||
row = matches.next();
|
||||
} else {
|
||||
// Search for the specified release.
|
||||
found = releases.find((release) => release.version === version);
|
||||
if (found === undefined) {
|
||||
if (values.verbose) {
|
||||
console.log(`Unable to find release data for Firefox ${version}. Searching full release data.`);
|
||||
}
|
||||
releases = await getFirefoxReleases(true);
|
||||
found = releases.find((release) => release.version === version);
|
||||
}
|
||||
}
|
||||
return releases;
|
||||
return found;
|
||||
};
|
||||
|
||||
const getLatestVersion = async (releases) => {
|
||||
const arrayNumberSortDescending = (x, y, i) => {
|
||||
if (x[i] === undefined && y[i] === undefined) {
|
||||
return 0;
|
||||
} else if (x[i] === y[i]) {
|
||||
return arrayNumberSortDescending(x, y, i + 1);
|
||||
}
|
||||
return (y[i] ?? 0) - (x[i] ?? 0);
|
||||
};
|
||||
const extractVersion = (t) => {
|
||||
return t[kNSSVersion].split('.').map((n) => parseInt(n));
|
||||
};
|
||||
const releaseSorter = (x, y) => {
|
||||
return arrayNumberSortDescending(extractVersion(x), extractVersion(y), 0);
|
||||
};
|
||||
// Return the most recent certadata.txt that exists on the server.
|
||||
const sortedReleases = releases.sort(releaseSorter).filter(pastRelease);
|
||||
for (const candidate of sortedReleases) {
|
||||
const candidateURL = getCertdataURL(candidate[kNSSVersion]);
|
||||
if (values.verbose) {
|
||||
console.log(`Trying ${candidateURL}`);
|
||||
}
|
||||
const response = await fetch(candidateURL, { method: 'HEAD' });
|
||||
if (response.ok) {
|
||||
return candidate[kNSSVersion];
|
||||
}
|
||||
const getNSSVersion = async (release) => {
|
||||
const latestFirefox = release.version;
|
||||
const firefoxTag = `FIREFOX_${latestFirefox.replace('.', '_')}_RELEASE`;
|
||||
const tagInfoURL = `https://hg.mozilla.org/releases/mozilla-release/raw-file/${firefoxTag}/security/nss/TAG-INFO`;
|
||||
if (values.verbose) {
|
||||
console.log(`Fetching NSS tag from ${tagInfoURL}.`);
|
||||
}
|
||||
};
|
||||
|
||||
const pastRelease = (r) => {
|
||||
return r[kNSSDate] < now;
|
||||
const tagInfo = await fetch(tagInfoURL);
|
||||
if (!tagInfo.ok) {
|
||||
console.error(`Failed to fetch ${tagInfoURL}: ${tagInfo.status}: ${tagInfo.statusText}`);
|
||||
}
|
||||
const tag = await tagInfo.text();
|
||||
if (values.verbose) {
|
||||
console.log(`Found tag ${tag}.`);
|
||||
}
|
||||
// Tag will be of form `NSS_x_y_RTM`. Convert to `x.y`.
|
||||
return tag.split('_').slice(1, -1).join('.');
|
||||
};
|
||||
|
||||
const options = {
|
||||
@ -135,9 +104,9 @@ const {
|
||||
});
|
||||
|
||||
if (values.help) {
|
||||
console.log(`Usage: ${basename(__filename)} [OPTION]... [VERSION]...`);
|
||||
console.log(`Usage: ${basename(__filename)} [OPTION]... [RELEASE]...`);
|
||||
console.log();
|
||||
console.log('Updates certdata.txt to NSS VERSION (most recent release by default).');
|
||||
console.log('Updates certdata.txt to NSS version contained in Firefox RELEASE (default: most recent release).');
|
||||
console.log('');
|
||||
console.log(' -f, --file=FILE writes a commit message reflecting the change to the');
|
||||
console.log(' specified FILE');
|
||||
@ -146,29 +115,11 @@ if (values.help) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const scheduleURL = 'https://wiki.mozilla.org/NSS:Release_Versions';
|
||||
if (values.verbose) {
|
||||
console.log(`Fetching NSS release schedule from ${scheduleURL}`);
|
||||
}
|
||||
const schedule = await fetch(scheduleURL);
|
||||
if (!schedule.ok) {
|
||||
console.error(`Failed to fetch ${scheduleURL}: ${schedule.status}: ${schedule.statusText}`);
|
||||
process.exit(-1);
|
||||
}
|
||||
const scheduleText = await schedule.text();
|
||||
const nssReleases = getReleases(scheduleText);
|
||||
|
||||
const firefoxRelease = await getFirefoxRelease(positionals[0]);
|
||||
// Retrieve metadata for the NSS release being updated to.
|
||||
const version = positionals[0] ?? await getLatestVersion(nssReleases);
|
||||
const release = nssReleases.find((r) => {
|
||||
return new RegExp(`^${version.replace('.', '\\.')}\\b`).test(r[kNSSVersion]);
|
||||
});
|
||||
if (!pastRelease(release)) {
|
||||
console.warn(`Warning: NSS ${version} is not due to be released until ${formatDate(release[kNSSDate])}`);
|
||||
}
|
||||
const version = await getNSSVersion(firefoxRelease);
|
||||
if (values.verbose) {
|
||||
console.log('Found NSS version:');
|
||||
console.log(release);
|
||||
console.log(`Updating to NSS version ${version}`);
|
||||
}
|
||||
|
||||
// Fetch certdata.txt and overwrite the local copy.
|
||||
@ -213,14 +164,15 @@ const added = [ ...diff.matchAll(certsAddedRE) ].map((m) => m[1]);
|
||||
const removed = [ ...diff.matchAll(certsRemovedRE) ].map((m) => m[1]);
|
||||
|
||||
const commitMsg = [
|
||||
`crypto: update root certificates to NSS ${release[kNSSVersion]}`,
|
||||
`crypto: update root certificates to NSS ${version}`,
|
||||
'',
|
||||
`This is the certdata.txt[0] from NSS ${release[kNSSVersion]}, released on ${formatDate(release[kNSSDate])}.`,
|
||||
'',
|
||||
`This is the version of NSS that ${release[kFirefoxDate] < now ? 'shipped' : 'will ship'} in Firefox ${release[kFirefoxVersion]} on`,
|
||||
`${formatDate(release[kFirefoxDate])}.`,
|
||||
`This is the certdata.txt[0] from NSS ${version}.`,
|
||||
'',
|
||||
];
|
||||
if (firefoxRelease) {
|
||||
commitMsg.push(`This is the version of NSS that shipped in Firefox ${firefoxRelease.version} on ${firefoxRelease.release_date}.`);
|
||||
commitMsg.push('');
|
||||
}
|
||||
if (added.length > 0) {
|
||||
commitMsg.push('Certificates added:');
|
||||
commitMsg.push(...added.map((cert) => `- ${cert}`));
|
||||
@ -234,7 +186,7 @@ if (removed.length > 0) {
|
||||
commitMsg.push(`[0] ${certdataURL}`);
|
||||
const delimiter = randomUUID();
|
||||
const properties = [
|
||||
`NEW_VERSION=${release[kNSSVersion]}`,
|
||||
`NEW_VERSION=${version}`,
|
||||
`COMMIT_MSG<<${delimiter}`,
|
||||
...commitMsg,
|
||||
delimiter,
|
||||
|
Loading…
Reference in New Issue
Block a user