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

Verify checksum during post-install hook #119

Merged
merged 2 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,26 @@ jobs:
cp target/${{ matrix.triple.target }}/release/monorepo builds/typescript-tools-${{ matrix.triple.target }}/
tar -C builds -czvf dist/typescript-tools-${{ matrix.triple.target }}.tar.gz typescript-tools-${{ matrix.triple.target }}

- name: Create checksum
# Create a checksum of the tar archive for manual checksum verification of the archive before extraction.
- name: Create tar checksum
run: shasum --algorithm 256 typescript-tools-${{ matrix.triple.target }}.tar.gz > typescript-tools-${{ matrix.triple.target }}-SHASUMS256.txt
working-directory: ./dist

# Create a checksum of the binary for automatic checksum verification in the post-install hook. In this code
# path, the tar archive is streamed from the GitHub release page and the stream is untarred before a file
# is ever written to disk, to minimize disk usage. This makes it impossible to validate the checksum of the
# entire tar archive, so we calculate a checksum of the untarred binary instead.
- name: Create binary checksum
run: shasum --algorithm 256 --binary monorepo | tee ../../dist/typescript-tools-${{ matrix.triple.target }}-binary-SHASUMS256.txt
working-directory: ./builds/typescript-tools-${{ matrix.triple.target }}

- name: Upload release artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.triple.target }}
path: |
dist/typescript-tools-${{ matrix.triple.target }}.tar.gz
dist/typescript-tools-${{ matrix.triple.target }}-binary-SHASUMS256.txt
dist/typescript-tools-${{ matrix.triple.target }}-SHASUMS256.txt
if-no-files-found: error
retention-days: 1
Expand Down Expand Up @@ -180,8 +190,7 @@ jobs:
working-directory: dist

- name: Combine checksums
run: cat **/typescript-tools-*-SHASUMS256.txt | tee SHASUMS256.txt
working-directory: dist
run: cat dist/**/typescript-tools-*-SHASUMS256.txt | tee npm/SHASUMS256.txt

- name: Invoke semantic-release
env:
Expand Down
2 changes: 1 addition & 1 deletion .releaserc.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"label": "aarch64-apple-darwin"
},
{
"path": "dist/SHASUMS256.txt",
"path": "npm/SHASUMS256.txt",
"label": "SHASUMS256.txt"
}
]
Expand Down
12 changes: 9 additions & 3 deletions bin/monorepo
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# This is a dummy binary that is the subject of the `npm install` workflow --
# it gets linked into node_modules/.bin and marked as executable before the
# npm postInstall hook executes
#!/bin/sh

cat <<EOF
This is not the "monorepo" binary, which is usually downloaded via the npm "postinstall"
hook. Was the "npm install" run with the "--ignore-scripts" flag?

Please try reinstalling "@typescript-tools/rust-implementation" or opening a ticket with
details about your use case.
EOF
45 changes: 34 additions & 11 deletions npm/binary-install.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
const { existsSync, mkdirSync } = require("fs");
const { createHash } = require("crypto");
const { chmodSync, existsSync, mkdirSync, readFileSync } = require("fs");
const { join } = require("path");
const { spawnSync } = require("child_process");

const axios = require("axios");
const tar = require("tar");
const rimraf = require("rimraf");

const error = msg => {
console.error(msg);
process.exit(1);
};

class Binary {
constructor(name, url) {
let errors = [];
Expand Down Expand Up @@ -38,7 +34,8 @@ class Binary {
});
errorMsg +=
'\n\nCorrect usage: new Binary("my-binary", "https://example.com/binary/download.tar.gz")';
error(errorMsg);
console.error(errorMsg);
process.exit(1);
}
this.url = url;
this.name = name;
Expand All @@ -60,7 +57,9 @@ class Binary {

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

// Stream the file from the GitHub release page
return axios({ ...fetchOptions, url: this.url, responseType: "stream" })
// untar the stream and write to disk
.then(res => {
return new Promise((resolve, reject) => {
const sink = tar.x({ strip: 1, C: this.installDirectory });
Expand All @@ -69,17 +68,40 @@ class Binary {
sink.on('error', err => reject(err));
});
})
// calculate a checksum of the untarred binary
.then(() => {
const fileBuffer = readFileSync(this.binaryPath);
const hashsum = createHash("sha256");
hashsum.update(fileBuffer);
const calculated_checksum = hashsum.digest('hex');

const advertised_checksums = readFileSync(join(__dirname, "SHASUMS256.txt"));

return new Promise((resolve, reject) => {
if (advertised_checksums.includes(calculated_checksum)) {
resolve();
} else {
chmodSync(this.binaryPath, 0o400);
console.error(`Calculated unexpected checksum ${calculated_checksum} for file ${this.binaryPath}`);
console.error('This file has been stripped of executable permissions but you should quarantine or delete it and open an issue.');
reject(new Error('Unexpected checksum'));
}
});
})
.then(() => {
// console.log(`${this.name} has been installed!`);
})
.catch(e => {
error(`Error fetching release: ${e.message}`);
.catch(err => {
console.error(`Error fetching release:`);
console.error(err);
process.exit(1);
});
}

run() {
if (!existsSync(this.binaryPath)) {
error(`You must install ${this.name} before you can run it`);
console.error(`You must install ${this.name} before you can run it`);
process.exit(1);
}

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

if (result.error) {
error(result.error);
console.error(result.error);
process.exit(1);
}

process.exit(result.status);
Expand Down