| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 6f0f61a commit c57dabe
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -169,13 +169,22 @@ jobs: | |||
| 169 | 169 | cat temp-output | |
| 170 | 170 | tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true | |
| 171 | 171 | rm temp-output | |
| 172 | + - id: root-certificates | ||
| 173 | + subsystem: crypto | ||
| 174 | + label: crypto, notable-change | ||
| 175 | + run: | | ||
| 176 | + node ./tools/dep_updaters/update-root-certs.mjs -v -f "$GITHUB_ENV" | ||
| 172 | 177 | steps: | |
| 173 | 178 | - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 | |
| 174 | 179 | with: | |
| 175 | 180 | persist-credentials: false | |
| 176 | 181 | - run: ${{ matrix.run }} | |
| 177 | 182 | env: | |
| 178 | 183 | GITHUB_TOKEN: ${{ secrets.GH_USER_TOKEN }} | |
| 184 | + - name: Generate commit message if not set | ||
| 185 | + if: ${{ env.COMMIT_MSG == '' }} | ||
| 186 | + run: | | ||
| 187 | + echo "COMMIT_MSG=${{ matrix.subsystem }}: update ${{ matrix.id }} to ${{ env.NEW_VERSION }}" >> "$GITHUB_ENV" | ||
| 179 | 188 | - uses: gr2m/create-or-update-pull-request-action@77596e3166f328b24613f7082ab30bf2d93079d5 | |
| 180 | 189 | # Creates a PR or update the Action's existing PR, or | |
| 181 | 190 | # no-op if the base branch is already up-to-date. | |
@@ -185,6 +194,6 @@ jobs: | |||
| 185 | 194 | author: Node.js GitHub Bot <github-bot@iojs.org> | |
| 186 | 195 | body: This is an automated update of ${{ matrix.id }} to ${{ env.NEW_VERSION }}. | |
| 187 | 196 | branch: actions/tools-update-${{ matrix.id }} # Custom branch *just* for this Action. | |
| 188 | - commit-message: '${{ matrix.subsystem }}: update ${{ matrix.id }} to ${{ env.NEW_VERSION }}' | ||
| 197 | + commit-message: ${{ env.COMMIT_MSG }} | ||
| 189 | 198 | labels: ${{ matrix.label }} | |
| 190 | 199 | title: '${{ matrix.subsystem }}: update ${{ matrix.id }} to ${{ env.NEW_VERSION }}' | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,6 +15,19 @@ check the [NSS release schedule][]. | |||
| 15 | 15 | ||
| 16 | 16 | ## Process | |
| 17 | 17 | ||
| 18 | + The `tools/dep_updaters/update-root-certs.mjs` script automates the update of | ||
| 19 | + the root certificates, including: | ||
| 20 | + | ||
| 21 | + * Downloading `certdata.txt` from Mozilla's source control repository. | ||
| 22 | + * Running `tools/mk-ca-bundle.pl` to convert the certificates and generate | ||
| 23 | + `src/node_root_certs.h`. | ||
| 24 | + * Using `git diff-files` to determine which certificate have been added and/or | ||
| 25 | + removed. | ||
| 26 | + | ||
| 27 | + Manual instructions are included in the following collapsed section. | ||
| 28 | + | ||
| 29 | + <details> | ||
| 30 | + | ||
| 18 | 31 | Commands assume that the current working directory is the root of a checkout of | |
| 19 | 32 | the nodejs/node repository. | |
| 20 | 33 | ||
@@ -121,5 +134,7 @@ the nodejs/node repository. | |||
| 121 | 134 | - OpenTrust Root CA G3 | |
| 122 | 135 | ``` | |
| 123 | 136 | ||
| 137 | + </details> | ||
| 138 | + | ||
| 124 | 139 | [NSS release schedule]: https://wiki.mozilla.org/NSS:Release_Versions | |
| 125 | 140 | [tag list]: https://hg.mozilla.org/projects/nss/tags | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,234 @@ | |||
| 1 | + // Script to update certdata.txt from NSS. | ||
| 2 | + import { execFileSync } from 'node:child_process'; | ||
| 3 | + import { randomUUID } from 'node:crypto'; | ||
| 4 | + import { createWriteStream } from 'node:fs'; | ||
| 5 | + import { basename, join, relative } from 'node:path'; | ||
| 6 | + import { Readable } from 'node:stream'; | ||
| 7 | + import { pipeline } from 'node:stream/promises'; | ||
| 8 | + import { fileURLToPath } from 'node:url'; | ||
| 9 | + import { parseArgs } from 'node:util'; | ||
| 10 | + | ||
| 11 | + // Constants for NSS release metadata. | ||
| 12 | + const kNSSVersion = 'version'; | ||
| 13 | + const kNSSDate = 'date'; | ||
| 14 | + const kFirefoxVersion = 'firefoxVersion'; | ||
| 15 | + const kFirefoxDate = 'firefoxDate'; | ||
| 16 | + | ||
| 17 | + const __filename = fileURLToPath(import.meta.url); | ||
| 18 | + const now = new Date(); | ||
| 19 | + | ||
| 20 | + const formatDate = (d) => { | ||
| 21 | + const iso = d.toISOString(); | ||
| 22 | + return iso.substring(0, iso.indexOf('T')); | ||
| 23 | + }; | ||
| 24 | + | ||
| 25 | + const normalizeTD = (text) => { | ||
| 26 | + // Remove whitespace and any HTML tags. | ||
| 27 | + return text?.trim().replace(/<.*?>/g, ''); | ||
| 28 | + }; | ||
| 29 | + const getReleases = (text) => { | ||
| 30 | + const releases = []; | ||
| 31 | + const tableRE = /<table [^>]+>([\S\s]*?)<\/table>/g; | ||
| 32 | + const tableRowRE = /<tr ?[^>]*>([\S\s]*?)<\/tr>/g; | ||
| 33 | + const tableHeaderRE = /<th ?[^>]*>([\S\s]*?)<\/th>/g; | ||
| 34 | + const tableDataRE = /<td ?[^>]*>([\S\s]*?)<\/td>/g; | ||
| 35 | + for (const table of text.matchAll(tableRE)) { | ||
| 36 | + const columns = {}; | ||
| 37 | + const matches = table[1].matchAll(tableRowRE); | ||
| 38 | + // First row has the table header. | ||
| 39 | + let row = matches.next(); | ||
| 40 | + if (row.done) { | ||
| 41 | + continue; | ||
| 42 | + } | ||
| 43 | + const headers = Array.from(row.value[1].matchAll(tableHeaderRE), (m) => m[1]); | ||
| 44 | + if (headers.length > 0) { | ||
| 45 | + for (let i = 0; i < headers.length; i++) { | ||
| 46 | + if (/NSS version/i.test(headers[i])) { | ||
| 47 | + columns[kNSSVersion] = i; | ||
| 48 | + } else if (/Release.*from branch/i.test(headers[i])) { | ||
| 49 | + columns[kNSSDate] = i; | ||
| 50 | + } else if (/Firefox version/i.test(headers[i])) { | ||
| 51 | + columns[kFirefoxVersion] = i; | ||
| 52 | + } else if (/Firefox release date/i.test(headers[i])) { | ||
| 53 | + columns[kFirefoxDate] = i; | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + // Filter out "NSS Certificate bugs" table. | ||
| 58 | + if (columns[kNSSDate] === undefined) { | ||
| 59 | + continue; | ||
| 60 | + } | ||
| 61 | + // Scrape releases. | ||
| 62 | + row = matches.next(); | ||
| 63 | + while (!row.done) { | ||
| 64 | + const cells = Array.from(row.value[1].matchAll(tableDataRE), (m) => m[1]); | ||
| 65 | + const release = {}; | ||
| 66 | + release[kNSSVersion] = normalizeTD(cells[columns[kNSSVersion]]); | ||
| 67 | + release[kNSSDate] = new Date(normalizeTD(cells[columns[kNSSDate]])); | ||
| 68 | + release[kFirefoxVersion] = normalizeTD(cells[columns[kFirefoxVersion]]); | ||
| 69 | + release[kFirefoxDate] = new Date(normalizeTD(cells[columns[kFirefoxDate]])); | ||
| 70 | + releases.push(release); | ||
| 71 | + row = matches.next(); | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | + return releases; | ||
| 75 | + }; | ||
| 76 | + | ||
| 77 | + const getLatestVersion = (releases) => { | ||
| 78 | + const arrayNumberSort = (x, y, i) => { | ||
| 79 | + if (x[i] === undefined && y[i] === undefined) { | ||
| 80 | + return 0; | ||
| 81 | + } else if (x[i] === y[i]) { | ||
| 82 | + return arrayNumberSort(x, y, i + 1); | ||
| 83 | + } | ||
| 84 | + return (x[i] ?? 0) - (y[i] ?? 0); | ||
| 85 | + }; | ||
| 86 | + const extractVersion = (t) => { | ||
| 87 | + return t[kNSSVersion].split('.').map((n) => parseInt(n)); | ||
| 88 | + }; | ||
| 89 | + const releaseSorter = (x, y) => { | ||
| 90 | + return arrayNumberSort(extractVersion(x), extractVersion(y), 0); | ||
| 91 | + }; | ||
| 92 | + return releases.sort(releaseSorter).filter(pastRelease).at(-1)[kNSSVersion]; | ||
| 93 | + }; | ||
| 94 | + | ||
| 95 | + const pastRelease = (r) => { | ||
| 96 | + return r[kNSSDate] < now; | ||
| 97 | + }; | ||
| 98 | + | ||
| 99 | + const options = { | ||
| 100 | + help: { | ||
| 101 | + type: 'boolean', | ||
| 102 | + }, | ||
| 103 | + file: { | ||
| 104 | + short: 'f', | ||
| 105 | + type: 'string', | ||
| 106 | + }, | ||
| 107 | + verbose: { | ||
| 108 | + short: 'v', | ||
| 109 | + type: 'boolean', | ||
| 110 | + }, | ||
| 111 | + }; | ||
| 112 | + const { | ||
| 113 | + positionals, | ||
| 114 | + values, | ||
| 115 | + } = parseArgs({ | ||
| 116 | + allowPositionals: true, | ||
| 117 | + options, | ||
| 118 | + }); | ||
| 119 | + | ||
| 120 | + if (values.help) { | ||
| 121 | + console.log(`Usage: ${basename(__filename)} [OPTION]... [VERSION]...`); | ||
| 122 | + console.log(); | ||
| 123 | + console.log('Updates certdata.txt to NSS VERSION (most recent release by default).'); | ||
| 124 | + console.log(''); | ||
| 125 | + console.log(' -f, --file=FILE writes a commit message reflecting the change to the'); | ||
| 126 | + console.log(' specified FILE'); | ||
| 127 | + console.log(' -v, --verbose writes progress to stdout'); | ||
| 128 | + console.log(' --help display this help and exit'); | ||
| 129 | + process.exit(0); | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + if (values.verbose) { | ||
| 133 | + console.log('Fetching NSS release schedule'); | ||
| 134 | + } | ||
| 135 | + const scheduleURL = 'https://wiki.mozilla.org/NSS:Release_Versions'; | ||
| 136 | + const schedule = await fetch(scheduleURL); | ||
| 137 | + if (!schedule.ok) { | ||
| 138 | + console.error(`Failed to fetch ${scheduleURL}: ${schedule.status}: ${schedule.statusText}`); | ||
| 139 | + process.exit(-1); | ||
| 140 | + } | ||
| 141 | + const scheduleText = await schedule.text(); | ||
| 142 | + const nssReleases = getReleases(scheduleText); | ||
| 143 | + | ||
| 144 | + // Retrieve metadata for the NSS release being updated to. | ||
| 145 | + const version = positionals[0] ?? getLatestVersion(nssReleases); | ||
| 146 | + const release = nssReleases.find((r) => { | ||
| 147 | + return new RegExp(`^${version.replace('.', '\\.')}\\b`).test(r[kNSSVersion]); | ||
| 148 | + }); | ||
| 149 | + if (!pastRelease(release)) { | ||
| 150 | + console.warn(`Warning: NSS ${version} is not due to be released until ${formatDate(release[kNSSDate])}`); | ||
| 151 | + } | ||
| 152 | + if (values.verbose) { | ||
| 153 | + console.log('Found NSS version:'); | ||
| 154 | + console.log(release); | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + // Fetch certdata.txt and overwrite the local copy. | ||
| 158 | + const tag = `NSS_${version.replaceAll('.', '_')}_RTM`; | ||
| 159 | + const certdataURL = `https://hg.mozilla.org/projects/nss/raw-file/${tag}/lib/ckfw/builtins/certdata.txt`; | ||
| 160 | + if (values.verbose) { | ||
| 161 | + console.log(`Fetching ${certdataURL}`); | ||
| 162 | + } | ||
| 163 | + const checkoutDir = join(__filename, '..', '..', '..'); | ||
| 164 | + const certdata = await fetch(certdataURL); | ||
| 165 | + const certdataFile = join(checkoutDir, 'tools', 'certdata.txt'); | ||
| 166 | + if (!certdata.ok) { | ||
| 167 | + console.error(`Failed to fetch ${certdataURL}: ${certdata.status}: ${certdata.statusText}`); | ||
| 168 | + process.exit(-1); | ||
| 169 | + } | ||
| 170 | + if (values.verbose) { | ||
| 171 | + console.log(`Writing ${certdataFile}`); | ||
| 172 | + } | ||
| 173 | + await pipeline(certdata.body, createWriteStream(certdataFile)); | ||
| 174 | + | ||
| 175 | + // Run tools/mk-ca-bundle.pl to generate src/node_root_certs.h. | ||
| 176 | + if (values.verbose) { | ||
| 177 | + console.log('Running tools/mk-ca-bundle.pl'); | ||
| 178 | + } | ||
| 179 | + const opts = { encoding: 'utf8' }; | ||
| 180 | + const mkCABundleTool = join(checkoutDir, 'tools', 'mk-ca-bundle.pl'); | ||
| 181 | + const mkCABundleOut = execFileSync(mkCABundleTool, | ||
| 182 | + values.verbose ? [ '-v' ] : [], | ||
| 183 | + opts); | ||
| 184 | + if (values.verbose) { | ||
| 185 | + console.log(mkCABundleOut); | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + // Determine certificates added and/or removed. | ||
| 189 | + const certHeaderFile = relative(process.cwd(), join(checkoutDir, 'src', 'node_root_certs.h')); | ||
| 190 | + const diff = execFileSync('git', [ 'diff-files', '-u', '--', certHeaderFile ], opts); | ||
| 191 | + if (values.verbose) { | ||
| 192 | + console.log(diff); | ||
| 193 | + } | ||
| 194 | + const certsAddedRE = /^\+\/\* (.*) \*\//gm; | ||
| 195 | + const certsRemovedRE = /^-\/\* (.*) \*\//gm; | ||
| 196 | + const added = [ ...diff.matchAll(certsAddedRE) ].map((m) => m[1]); | ||
| 197 | + const removed = [ ...diff.matchAll(certsRemovedRE) ].map((m) => m[1]); | ||
| 198 | + | ||
| 199 | + const commitMsg = [ | ||
| 200 | + `crypto: update root certificates to NSS ${release[kNSSVersion]}`, | ||
| 201 | + '', | ||
| 202 | + `This is the certdata.txt[0] from NSS ${release[kNSSVersion]}, released on ${formatDate(release[kNSSDate])}.`, | ||
| 203 | + '', | ||
| 204 | + `This is the version of NSS that ${release[kFirefoxDate] < now ? 'shipped' : 'will ship'} in Firefox ${release[kFirefoxVersion]} on`, | ||
| 205 | + `${formatDate(release[kFirefoxDate])}.`, | ||
| 206 | + '', | ||
| 207 | + ]; | ||
| 208 | + if (added.length > 0) { | ||
| 209 | + commitMsg.push('Certificates added:'); | ||
| 210 | + commitMsg.push(...added.map((cert) => `- ${cert}`)); | ||
| 211 | + commitMsg.push(''); | ||
| 212 | + } | ||
| 213 | + if (removed.length > 0) { | ||
| 214 | + commitMsg.push('Certificates removed:'); | ||
| 215 | + commitMsg.push(...removed.map((cert) => `- ${cert}`)); | ||
| 216 | + commitMsg.push(''); | ||
| 217 | + } | ||
| 218 | + commitMsg.push(`[0] ${certdataURL}`); | ||
| 219 | + const delimiter = randomUUID(); | ||
| 220 | + const properties = [ | ||
| 221 | + `NEW_VERSION=${release[kNSSVersion]}`, | ||
| 222 | + `COMMIT_MSG<<${delimiter}`, | ||
| 223 | + ...commitMsg, | ||
| 224 | + delimiter, | ||
| 225 | + '', | ||
| 226 | + ].join('\n'); | ||
| 227 | + if (values.verbose) { | ||
| 228 | + console.log(properties); | ||
| 229 | + } | ||
| 230 | + const propertyFile = values.file; | ||
| 231 | + if (propertyFile !== undefined) { | ||
| 232 | + console.log(`Writing to ${propertyFile}`); | ||
| 233 | + await pipeline(Readable.from(properties), createWriteStream(propertyFile)); | ||
| 234 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments