@@ -12,6 +12,7 @@ const http = require('http');
1212const fs = require ( 'fs' ) ;
1313const path = require ( 'path' ) ;
1414const os = require ( 'os' ) ;
15+ const crypto = require ( 'crypto' ) ;
1516const { execSync } = require ( 'child_process' ) ;
1617
1718const VERSION = require ( '../package.json' ) . version ;
@@ -65,6 +66,50 @@ async function download(url, dest) {
6566 } ) ;
6667}
6768
69+ function sha256File ( filePath ) {
70+ const hasher = crypto . createHash ( 'sha256' ) ;
71+ hasher . update ( fs . readFileSync ( filePath ) ) ;
72+ return hasher . digest ( 'hex' ) ;
73+ }
74+
75+ function readExpectedSha256 ( checksumPath , assetName ) {
76+ const checksumText = fs . readFileSync ( checksumPath , 'utf8' ) . trim ( ) ;
77+ if ( ! checksumText ) {
78+ throw new Error ( `Checksum file was empty: ${ checksumPath } ` ) ;
79+ }
80+
81+ const lines = checksumText . split ( / \r ? \n / ) . map ( ( line ) => line . trim ( ) ) . filter ( Boolean ) ;
82+ for ( const line of lines ) {
83+ // Format: "<sha256> <filename>" (or optional "*" marker)
84+ let match = line . match ( / ^ ( [ a - f A - F 0 - 9 ] { 64 } ) \s + \* ? ( .+ ) $ / ) ;
85+ if ( match ) {
86+ const digest = match [ 1 ] . toLowerCase ( ) ;
87+ const fileName = path . basename ( match [ 2 ] . trim ( ) ) ;
88+ if ( fileName === assetName ) {
89+ return digest ;
90+ }
91+ }
92+
93+ // Format: "SHA256(<filename>)=<sha256>"
94+ match = line . match ( / ^ S H A 2 5 6 \s * \( ( .+ ) \) \s * = \s * ( [ a - f A - F 0 - 9 ] { 64 } ) $ / i) ;
95+ if ( match ) {
96+ const fileName = path . basename ( match [ 1 ] . trim ( ) ) ;
97+ const digest = match [ 2 ] . toLowerCase ( ) ;
98+ if ( fileName === assetName ) {
99+ return digest ;
100+ }
101+ }
102+
103+ // Format: "<sha256>" (single-line digest file)
104+ match = line . match ( / ^ ( [ a - f A - F 0 - 9 ] { 64 } ) $ / ) ;
105+ if ( match && lines . length === 1 ) {
106+ return match [ 1 ] . toLowerCase ( ) ;
107+ }
108+ }
109+
110+ throw new Error ( `Checksum for ${ assetName } not found in ${ checksumPath } ` ) ;
111+ }
112+
68113async function main ( ) {
69114 const key = getPlatformKey ( ) ;
70115 if ( ! key ) {
@@ -76,6 +121,7 @@ async function main() {
76121 const ext = isWindows ? 'zip' : 'tar.gz' ;
77122 const assetName = `jacs-cli-${ VERSION } -${ key } .${ ext } ` ;
78123 const url = `https://github.com/${ REPO } /releases/download/cli/v${ VERSION } /${ assetName } ` ;
124+ const checksumUrl = `${ url } .sha256` ;
79125
80126 const binDir = getBinDir ( ) ;
81127 const binPath = path . join ( binDir , getBinName ( ) ) ;
@@ -90,9 +136,19 @@ async function main() {
90136
91137 const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'jacs-cli-' ) ) ;
92138 const archivePath = path . join ( tmpDir , assetName ) ;
139+ const checksumPath = path . join ( tmpDir , `${ assetName } .sha256` ) ;
93140
94141 try {
142+ console . log ( `[jacs] Downloading checksum for pinned version ${ VERSION } from ${ checksumUrl } ` ) ;
143+ await download ( checksumUrl , checksumPath ) ;
95144 await download ( url , archivePath ) ;
145+ const expectedSha256 = readExpectedSha256 ( checksumPath , assetName ) ;
146+ const actualSha256 = sha256File ( archivePath ) ;
147+ if ( expectedSha256 !== actualSha256 ) {
148+ throw new Error (
149+ `Checksum mismatch for ${ assetName } : expected ${ expectedSha256 } , got ${ actualSha256 } `
150+ ) ;
151+ }
96152
97153 fs . mkdirSync ( binDir , { recursive : true } ) ;
98154
0 commit comments