Skip to content
This repository was archived by the owner on Sep 17, 2023. It is now read-only.

Commit 28d75e7

Browse files
committed
fix: implement checksum verification during post-install
Closes #118
1 parent 1fdfc07 commit 28d75e7

File tree

3 files changed

+47
-15
lines changed

3 files changed

+47
-15
lines changed

.github/workflows/release.yml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,16 +140,26 @@ jobs:
140140
cp target/${{ matrix.triple.target }}/release/monorepo builds/typescript-tools-${{ matrix.triple.target }}/
141141
tar -C builds -czvf dist/typescript-tools-${{ matrix.triple.target }}.tar.gz typescript-tools-${{ matrix.triple.target }}
142142
143-
- name: Create checksum
143+
# Create a checksum of the tar archive for manual checksum verification of the archive before extraction.
144+
- name: Create tar checksum
144145
run: shasum --algorithm 256 typescript-tools-${{ matrix.triple.target }}.tar.gz > typescript-tools-${{ matrix.triple.target }}-SHASUMS256.txt
145146
working-directory: ./dist
146147

148+
# Create a checksum of the binary for automatic checksum verification in the post-install hook. In this code
149+
# path, the tar archive is streamed from the GitHub release page and the stream is untarred before a file
150+
# is ever written to disk, to minimize disk usage. This makes it impossible to validate the checksum of the
151+
# entire tar archive, so we calculate a checksum of the untarred binary instead.
152+
- name: Create binary checksum
153+
run: shasum --algorithm 256 --binary monorepo | tee ../../dist/typescript-tools-${{ matrix.triple.target }}-binary-SHASUMS256.txt
154+
working-directory: ./builds/typescript-tools-${{ matrix.triple.target }}
155+
147156
- name: Upload release artifacts
148157
uses: actions/upload-artifact@v3
149158
with:
150159
name: ${{ matrix.triple.target }}
151160
path: |
152161
dist/typescript-tools-${{ matrix.triple.target }}.tar.gz
162+
dist/typescript-tools-${{ matrix.triple.target }}-binary-SHASUMS256.txt
153163
dist/typescript-tools-${{ matrix.triple.target }}-SHASUMS256.txt
154164
if-no-files-found: error
155165
retention-days: 1
@@ -180,8 +190,7 @@ jobs:
180190
working-directory: dist
181191

182192
- name: Combine checksums
183-
run: cat **/typescript-tools-*-SHASUMS256.txt | tee SHASUMS256.txt
184-
working-directory: dist
193+
run: cat dist/**/typescript-tools-*-SHASUMS256.txt | tee npm/SHASUMS256.txt
185194

186195
- name: Invoke semantic-release
187196
env:

.releaserc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"label": "aarch64-apple-darwin"
5757
},
5858
{
59-
"path": "dist/SHASUMS256.txt",
59+
"path": "npm/SHASUMS256.txt",
6060
"label": "SHASUMS256.txt"
6161
}
6262
]

npm/binary-install.js

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
const { existsSync, mkdirSync } = require("fs");
1+
const { createHash } = require("crypto");
2+
const { chmodSync, existsSync, mkdirSync, readFileSync } = require("fs");
23
const { join } = require("path");
34
const { spawnSync } = require("child_process");
45

56
const axios = require("axios");
67
const tar = require("tar");
78
const rimraf = require("rimraf");
89

9-
const error = msg => {
10-
console.error(msg);
11-
process.exit(1);
12-
};
13-
1410
class Binary {
1511
constructor(name, url) {
1612
let errors = [];
@@ -38,7 +34,8 @@ class Binary {
3834
});
3935
errorMsg +=
4036
'\n\nCorrect usage: new Binary("my-binary", "https://example.com/binary/download.tar.gz")';
41-
error(errorMsg);
37+
console.error(errorMsg);
38+
process.exit(1);
4239
}
4340
this.url = url;
4441
this.name = name;
@@ -60,7 +57,9 @@ class Binary {
6057

6158
// console.log(`Downloading release from ${this.url}`);
6259

60+
// Stream the file from the GitHub release page
6361
return axios({ ...fetchOptions, url: this.url, responseType: "stream" })
62+
// untar the stream and write to disk
6463
.then(res => {
6564
return new Promise((resolve, reject) => {
6665
const sink = tar.x({ strip: 1, C: this.installDirectory });
@@ -69,17 +68,40 @@ class Binary {
6968
sink.on('error', err => reject(err));
7069
});
7170
})
71+
// calculate a checksum of the untarred binary
72+
.then(() => {
73+
const fileBuffer = readFileSync(this.binaryPath);
74+
const hashsum = createHash("sha256");
75+
hashsum.update(fileBuffer);
76+
const calculated_checksum = hashsum.digest('hex');
77+
78+
const advertised_checksums = readFileSync(join(__dirname, "SHASUMS256.txt"));
79+
80+
return new Promise((resolve, reject) => {
81+
if (advertised_checksums.includes(calculated_checksum)) {
82+
resolve();
83+
} else {
84+
chmodSync(this.binaryPath, 0o400);
85+
console.error(`Calculated unexpected checksum ${calculated_checksum} for file ${this.binaryPath}`);
86+
console.error('This file has been stripped of executable permissions but you should quarantine or delete it and open an issue.');
87+
reject(new Error('Unexpected checksum'));
88+
}
89+
});
90+
})
7291
.then(() => {
7392
// console.log(`${this.name} has been installed!`);
7493
})
75-
.catch(e => {
76-
error(`Error fetching release: ${e.message}`);
94+
.catch(err => {
95+
console.error(`Error fetching release:`);
96+
console.error(err);
97+
process.exit(1);
7798
});
7899
}
79100

80101
run() {
81102
if (!existsSync(this.binaryPath)) {
82-
error(`You must install ${this.name} before you can run it`);
103+
console.error(`You must install ${this.name} before you can run it`);
104+
process.exit(1);
83105
}
84106

85107
const [, , ...args] = process.argv;
@@ -89,7 +111,8 @@ class Binary {
89111
const result = spawnSync(this.binaryPath, args, options);
90112

91113
if (result.error) {
92-
error(result.error);
114+
console.error(result.error);
115+
process.exit(1);
93116
}
94117

95118
process.exit(result.status);

0 commit comments

Comments
 (0)