Skip to content

Commit aecf3cb

Browse files
committed
Remove dependency on node-forge and run test as electron
1 parent ebbbb9a commit aecf3cb

File tree

4 files changed

+492
-58
lines changed

4 files changed

+492
-58
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"pretest": "tsc -p . --outDir out && tsc -p test --outDir out && yarn run build && yarn run lint",
2828
"test": "vitest",
2929
"test:ci": "CI=true yarn test",
30+
"test:electron": "ELECTRON_RUN_AS_NODE=1 electron node_modules/vitest/vitest.mjs test/unit/error.test.ts",
3031
"test:integration": "vscode-test",
3132
"vscode:prepublish": "yarn package",
3233
"watch": "webpack --watch"
@@ -343,12 +344,12 @@
343344
"word-wrap": "1.2.5"
344345
},
345346
"dependencies": {
347+
"@peculiar/x509": "^1.14.0",
346348
"axios": "1.12.2",
347349
"date-fns": "^3.6.0",
348350
"eventsource": "^3.0.6",
349351
"find-process": "https://github.com/coder/find-process#fix/sequoia-compat",
350352
"jsonc-parser": "^3.3.1",
351-
"node-forge": "^1.3.1",
352353
"openpgp": "^6.2.2",
353354
"pretty-bytes": "^7.1.0",
354355
"proxy-agent": "^6.5.0",
@@ -361,7 +362,6 @@
361362
"@types/eventsource": "^3.0.0",
362363
"@types/glob": "^7.1.3",
363364
"@types/node": "^22.14.1",
364-
"@types/node-forge": "^1.3.14",
365365
"@types/semver": "^7.7.1",
366366
"@types/ua-parser-js": "0.7.36",
367367
"@types/vscode": "^1.73.0",
@@ -375,6 +375,7 @@
375375
"bufferutil": "^4.0.9",
376376
"coder": "https://github.com/coder/coder#main",
377377
"dayjs": "^1.11.13",
378+
"electron": "^39.1.2",
378379
"eslint": "^8.57.1",
379380
"eslint-config-prettier": "^10.1.8",
380381
"eslint-import-resolver-typescript": "^4.4.4",

src/error.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import {
2+
X509Certificate,
3+
KeyUsagesExtension,
4+
KeyUsageFlags,
5+
} from "@peculiar/x509";
16
import { isAxiosError } from "axios";
27
import { isApiError, isApiErrorResponse } from "coder/site/src/api/errors";
3-
import * as forge from "node-forge";
4-
import * as tls from "tls";
8+
import * as tls from "node:tls";
59
import * as vscode from "vscode";
610

711
import { type Logger } from "./logging/logger";
@@ -23,10 +27,6 @@ export enum X509_ERR {
2327
UNTRUSTED_CHAIN = "Your Coder deployment's certificate chain does not appear to be trusted by this system. The root of the certificate chain must be added to this system's trust store. ",
2428
}
2529

26-
interface KeyUsage {
27-
keyCertSign: boolean;
28-
}
29-
3030
export class CertificateError extends Error {
3131
public static ActionAllowInsecure = "Allow Insecure";
3232
public static ActionOK = "OK";
@@ -84,36 +84,38 @@ export class CertificateError extends Error {
8484
host: url.hostname,
8585
rejectUnauthorized: false,
8686
},
87-
() => {
87+
async () => {
8888
const x509 = socket.getPeerX509Certificate();
8989
socket.destroy();
9090
if (!x509) {
9191
throw new Error("no peer certificate");
9292
}
9393

94-
// We use node-forge for two reasons:
94+
// We use "@peculiar/x509" for two reasons:
9595
// 1. Node/Electron only provide extended key usage.
9696
// 2. Electron's checkIssued() will fail because it suffers from same
9797
// the key usage bug that we are trying to work around here in the
9898
// first place.
99-
const cert = forge.pki.certificateFromPem(x509.toString());
100-
if (!cert.issued(cert)) {
99+
const cert = new X509Certificate(x509.toString());
100+
const isSelfIssued = await cert.isSelfSigned();
101+
if (!isSelfIssued) {
101102
return resolve(X509_ERR.PARTIAL_CHAIN);
102103
}
103104

104105
// The key usage needs to exist but not have cert signing to fail.
105-
const keyUsage = cert.getExtension({ name: "keyUsage" }) as
106-
| KeyUsage
107-
| undefined;
108-
if (keyUsage && !keyUsage.keyCertSign) {
109-
return resolve(X509_ERR.NON_SIGNING);
110-
} else {
111-
// This branch is currently untested; it does not appear possible to
112-
// get the error "unable to verify" with a self-signed certificate
113-
// unless the key usage was the issue since it would have errored
114-
// with "self-signed certificate" instead.
115-
return resolve(X509_ERR.UNTRUSTED_LEAF);
106+
const extension = cert.getExtension(KeyUsagesExtension);
107+
if (extension) {
108+
const hasKeyCertSign =
109+
extension.usages & KeyUsageFlags.keyCertSign;
110+
if (!hasKeyCertSign) {
111+
return resolve(X509_ERR.NON_SIGNING);
112+
}
116113
}
114+
// This branch is currently untested; it does not appear possible to
115+
// get the error "unable to verify" with a self-signed certificate
116+
// unless the key usage was the issue since it would have errored
117+
// with "self-signed certificate" instead.
118+
return resolve(X509_ERR.UNTRUSTED_LEAF);
117119
},
118120
);
119121
socket.on("error", reject);

test/unit/error.test.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import axios from "axios";
2-
import * as fs from "fs/promises";
3-
import https from "https";
2+
import * as fs from "node:fs/promises";
3+
import https from "node:https";
44
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
55

66
import { CertificateError, X509_ERR, X509_ERR_CODE } from "@/error";
@@ -12,11 +12,8 @@ describe("Certificate errors", () => {
1212
// Before each test we make a request to sanity check that we really get the
1313
// error we are expecting, then we run it through CertificateError.
1414

15-
// TODO: These sanity checks need to be ran in an Electron environment to
16-
// reflect real usage in VS Code. We should either revert back to the standard
17-
// extension testing framework which I believe runs in a headless VS Code
18-
// instead of using vitest or at least run the tests through Electron running as
19-
// Node (for now I do this manually by shimming Node).
15+
// Some tests behave differently in Electron (BoringSSL) vs Node.js (OpenSSL).
16+
// Run `yarn test:electron` to test Electron-specific behavior.
2017
const isElectron =
2118
(process.versions.electron || process.env.ELECTRON_RUN_AS_NODE) &&
2219
!process.env.VSCODE_PID; // Running from the test explorer in VS Code

0 commit comments

Comments
 (0)