mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
feat: mTLS client certificates for desktop server links
This commit is contained in:
1 parent
802b3003d5
commit
895488d400
9 files changed
+373
-12
No files matched your search
@@ -34,6 +34,8 @@ function setLinkedServer(config) {
|
||||
basicAuth:
|
||||
config.basicAuth && config.basicAuth.username ? config.basicAuth : null,
|
||||
allowInvalidCertificate: !!config.allowInvalidCertificate,
|
||||
clientCert:
|
||||
config.clientCert && config.clientCert.pfx ? config.clientCert : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,6 +47,16 @@ function isLinkedOrigin(url) {
|
||||
return !!linked && getOrigin(url) === linked.origin;
|
||||
}
|
||||
|
||||
// The PFX of the link's client certificate, for TLS this process makes itself
|
||||
// to the linked origin. Null for any other origin.
|
||||
function clientCertOptionsFor(url) {
|
||||
if (!isLinkedOrigin(url) || !linked.clientCert) return {};
|
||||
return {
|
||||
pfx: Buffer.from(linked.clientCert.pfx, "base64"),
|
||||
passphrase: linked.clientCert.passphrase || "",
|
||||
};
|
||||
}
|
||||
|
||||
function basicAuthValue(basicAuth) {
|
||||
return `Basic ${Buffer.from(
|
||||
`${basicAuth.username}:${basicAuth.password || ""}`,
|
||||
@@ -99,6 +111,7 @@ module.exports = {
|
||||
setLinkedServer,
|
||||
getLinkedServer,
|
||||
isLinkedOrigin,
|
||||
clientCertOptionsFor,
|
||||
applyLinkHeaders,
|
||||
linkedRequestHeaders,
|
||||
answerLogin,
|
||||
|
||||
@@ -459,6 +459,7 @@ function isInvalidCertificateAllowedForUrl(url) {
|
||||
function getTlsVerificationOptions(url) {
|
||||
return {
|
||||
rejectUnauthorized: !isInvalidCertificateAllowedForUrl(url),
|
||||
...linkedServer.clientCertOptionsFor(url),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -690,6 +691,39 @@ ipcMain.handle("allow-invalid-certificate-for-origin", (_event, origin) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Mutual TLS for the linked server: Chromium asks which certificate from the
|
||||
// OS store to present. Other origins keep Chromium's default (none).
|
||||
const chosenClientCerts = new Map();
|
||||
|
||||
app.on(
|
||||
"select-client-certificate",
|
||||
async (event, _webContents, url, list, callback) => {
|
||||
if (!linkedServer.isLinkedOrigin(url) || !list.length) return;
|
||||
event.preventDefault();
|
||||
const origin = new URL(url).origin;
|
||||
const remembered = list.find(
|
||||
(cert) => cert.fingerprint === chosenClientCerts.get(origin),
|
||||
);
|
||||
let chosen = remembered || (list.length === 1 ? list[0] : null);
|
||||
if (!chosen) {
|
||||
const { response } = await dialog.showMessageBox(mainWindow, {
|
||||
type: "question",
|
||||
title: "Client certificate",
|
||||
message: "Choose the certificate for this server",
|
||||
detail: origin,
|
||||
buttons: [
|
||||
...list.slice(0, 8).map((cert) => cert.subjectName),
|
||||
"Cancel",
|
||||
],
|
||||
cancelId: Math.min(list.length, 8),
|
||||
});
|
||||
chosen = list[response] || null;
|
||||
}
|
||||
if (chosen) chosenClientCerts.set(origin, chosen.fingerprint);
|
||||
callback(chosen || undefined);
|
||||
},
|
||||
);
|
||||
|
||||
app.on(
|
||||
"certificate-error",
|
||||
(event, _webContents, url, error, certificate, callback) => {
|
||||
|
||||
Reference in new issue
Block a user