Skip to content
This repository was archived by the owner on Nov 25, 2025. It is now read-only.

Commit 906009f

Browse files
committed
update minisign verification
1 parent 9c7bb76 commit 906009f

File tree

2 files changed

+70
-20
lines changed

2 files changed

+70
-20
lines changed

src/minisign.ts

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
/**
2-
* Ported from: https://github.com/mlugg/setup-zig/blob/main/main.js (MIT)
2+
* Ported from: https://github.com/mlugg/setup-zig/blob/main/minisign.js
3+
*
4+
* Copyright Matthew Lugg
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
323
*/
424

525
import sodium from "libsodium-wrappers";
@@ -9,6 +29,8 @@ export interface Key {
929
key: Buffer;
1030
}
1131

32+
export const ready = sodium.ready;
33+
1234
// Parse a minisign key represented as a base64 string.
1335
// Throws exceptions on invalid keys.
1436
export function parseKey(keyString: string): Key {
@@ -31,16 +53,19 @@ export interface Signature {
3153
algorithm: Buffer;
3254
keyID: Buffer;
3355
signature: Buffer;
56+
trustedComment: Buffer;
57+
globalSignature: Buffer;
3458
}
3559

3660
// Parse a buffer containing the contents of a minisign signature file.
3761
// Throws exceptions on invalid signature files.
3862
export function parseSignature(sigBuf: Buffer): Signature {
3963
const untrustedHeader = Buffer.from("untrusted comment: ");
64+
const trustedHeader = Buffer.from("trusted comment: ");
4065

4166
// Validate untrusted comment header, and skip
4267
if (!sigBuf.subarray(0, untrustedHeader.byteLength).equals(untrustedHeader)) {
43-
throw new Error("file format not recognised");
68+
throw new Error("invalid minisign signature: bad untrusted comment header");
4469
}
4570
sigBuf = sigBuf.subarray(untrustedHeader.byteLength);
4671

@@ -57,36 +82,59 @@ export function parseSignature(sigBuf: Buffer): Signature {
5782
const keyID = sigInfo.subarray(2, 10);
5883
const signature = sigInfo.subarray(10);
5984

60-
// We don't look at the trusted comment or global signature, so we're done.
85+
// Validate trusted comment header, and skip
86+
if (!sigBuf.subarray(0, trustedHeader.byteLength).equals(trustedHeader)) {
87+
throw new Error("invalid minisign signature: bad trusted comment header");
88+
}
89+
sigBuf = sigBuf.subarray(trustedHeader.byteLength);
90+
91+
// Read and skip trusted comment
92+
const trustedCommentEnd = sigBuf.indexOf("\n");
93+
const trustedComment = sigBuf.subarray(0, trustedCommentEnd);
94+
sigBuf = sigBuf.subarray(trustedCommentEnd + 1);
95+
96+
// Read and skip global signature; handle missing trailing newline, just in case
97+
let globalSigEnd = sigBuf.indexOf("\n");
98+
if (globalSigEnd === -1) globalSigEnd = sigBuf.length;
99+
const globalSig = Buffer.from(sigBuf.subarray(0, globalSigEnd).toString(), "base64");
100+
sigBuf = sigBuf.subarray(sigInfoEnd + 1); // this might be length+1, but that's allowed
101+
102+
// Validate that all data has been consumed
103+
if (sigBuf.length !== 0) {
104+
throw new Error("invalid minisign signature: trailing bytes");
105+
}
61106

62107
return {
63108
algorithm: algorithm,
64109
keyID: keyID,
65110
signature: signature,
111+
trustedComment: trustedComment,
112+
globalSignature: globalSig,
66113
};
67114
}
68115

69-
// Given a parsed key, parsed signature file, and raw file content, verifies the
70-
// signature. Does not throw. Returns 'true' if the signature is valid for this
71-
// file, 'false' otherwise.
116+
// Given a parsed key, parsed signature file, and raw file content, verifies the signature,
117+
// including the global signature (hence validating the trusted comment). Does not throw.
118+
// Returns 'true' if the signature is valid for this file, 'false' otherwise.
72119
export function verifySignature(pubkey: Key, signature: Signature, fileContent: Buffer) {
120+
if (!signature.keyID.equals(pubkey.id)) {
121+
return false;
122+
}
123+
73124
let signedContent;
74125
if (signature.algorithm.equals(Buffer.from("ED"))) {
75126
signedContent = sodium.crypto_generichash(sodium.crypto_generichash_BYTES_MAX, fileContent);
76127
} else {
77128
signedContent = fileContent;
78129
}
79-
80-
if (!signature.keyID.equals(pubkey.id)) {
130+
if (!sodium.crypto_sign_verify_detached(signature.signature, signedContent, pubkey.key)) {
81131
return false;
82132
}
83133

84-
if (!sodium.crypto_sign_verify_detached(signature.signature, signedContent, pubkey.key)) {
134+
const globalSignedContent = Buffer.concat([signature.signature, signature.trustedComment]);
135+
if (!sodium.crypto_sign_verify_detached(signature.globalSignature, globalSignedContent, pubkey.key)) {
85136
return false;
86137
}
87138

88-
// Since we don't use the trusted comment, we don't bother verifying the global signature.
89-
// If we were to start using the trusted comment for any purpose, we must add this.
90-
91139
return true;
92140
}

src/versionManager.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,22 +209,24 @@ async function installFromMirror(
209209

210210
progress.report({ message: "Verifying Signature..." });
211211

212+
await minisign.ready;
213+
212214
const signature = minisign.parseSignature(signatureData);
213215
if (!minisign.verifySignature(config.minisignKey, signature, artifactData)) {
214-
try {
215-
await vscode.workspace.fs.delete(installDir, { recursive: true, useTrash: false });
216-
} catch {}
217216
throw new Error(`signature verification failed for '${artifactUrl.toString()}'`);
218217
}
219218

220-
try {
221-
await vscode.workspace.fs.delete(installDir, { recursive: true, useTrash: false });
222-
} catch {}
223-
await vscode.workspace.fs.createDirectory(installDir);
224-
await vscode.workspace.fs.writeFile(tarballUri, artifactData);
219+
const match = /^timestamp:\d+\s+file:([^\s]+)\s+hashed$/.test(signature.trustedComment.toString());
220+
if (!match) {
221+
throw new Error(`filename verification failed for '${artifactUrl.toString()}'`);
222+
}
225223

226224
progress.report({ message: "Extracting..." });
225+
227226
try {
227+
await vscode.workspace.fs.delete(installDir, { recursive: true, useTrash: false });
228+
await vscode.workspace.fs.createDirectory(installDir);
229+
await vscode.workspace.fs.writeFile(tarballUri, artifactData);
228230
await execFile(tarPath, ["-xf", tarballUri.fsPath, "-C", installDir.fsPath].concat(config.extraTarArgs), {
229231
signal: abortController.signal,
230232
timeout: 60000, // 60 seconds

0 commit comments

Comments
 (0)