From 7ba50e83f2f283d0e12f2cdc4fba36129dcb61fb Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 3 May 2024 21:49:25 -0700 Subject: [PATCH] create an in-memory self-signed tls cert on the golang side and trust it on the electron side. enables http/2 protocol for increased throughput (simultaneous connections) --- src/app/appconst.ts | 4 +-- src/electron/emain.ts | 30 ++++++++++++++++---- wavesrv/cmd/main-server.go | 18 +++++++++++- wavesrv/pkg/waveenc/cert.go | 55 +++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 wavesrv/pkg/waveenc/cert.go diff --git a/src/app/appconst.ts b/src/app/appconst.ts index 0b517933..090d42b7 100644 --- a/src/app/appconst.ts +++ b/src/app/appconst.ts @@ -21,9 +21,9 @@ export const NoStrPos = -1; export const RemotePtyRows = 8; export const RemotePtyTotalRows = 25; export const RemotePtyCols = 80; -export const ProdServerEndpoint = "http://127.0.0.1:1619"; +export const ProdServerEndpoint = "https://127.0.0.1:1619"; export const ProdServerWsEndpoint = "ws://127.0.0.1:1623"; -export const DevServerEndpoint = "http://127.0.0.1:8090"; +export const DevServerEndpoint = "https://127.0.0.1:8090"; export const DevServerWsEndpoint = "ws://127.0.0.1:8091"; export const DefaultTermFontSize = 13; export const DefaultTermFontFamily = "Hack"; diff --git a/src/electron/emain.ts b/src/electron/emain.ts index b5b3cea0..e52bdc18 100644 --- a/src/electron/emain.ts +++ b/src/electron/emain.ts @@ -16,12 +16,13 @@ import { handleJsonFetchResponse, fireAndForget } from "@/util/util"; import { v4 as uuidv4 } from "uuid"; import { adaptFromElectronKeyEvent, setKeyUtilPlatform } from "@/util/keyutil"; import { platform } from "os"; +import * as https from "https"; const WaveAppPathVarName = "WAVETERM_APP_PATH"; const WaveDevVarName = "WAVETERM_DEV"; const AuthKeyFile = "waveterm.authkey"; -const DevServerEndpoint = "http://127.0.0.1:8090"; -const ProdServerEndpoint = "http://127.0.0.1:1619"; +const DevServerEndpoint = "https://127.0.0.1:8090"; +const ProdServerEndpoint = "https://127.0.0.1:1619"; const isDev = process.env[WaveDevVarName] != null; const waveHome = getWaveHomeDir(); @@ -36,6 +37,10 @@ let currentGlobalShortcut: string | null = null; let initialClientData: ClientDataType = null; let windows: Windows = {}; +const httpsAgentIgnoreCert = new https.Agent({ + rejectUnauthorized: false, +}); + interface Windows extends Record {} checkPromptMigrate(); @@ -418,7 +423,7 @@ function mainResizeHandler(_: any, win: Electron.BrowserWindow) { const winSize = { width: bounds.width, height: bounds.height, top: bounds.y, left: bounds.x }; const url = new URL(getBaseHostPort() + "/api/set-winsize"); const fetchHeaders = getFetchHeaders(); - fetch(url, { method: "post", body: JSON.stringify(winSize), headers: fetchHeaders }) + fetch(url, { method: "post", body: JSON.stringify(winSize), headers: fetchHeaders, agent: httpsAgentIgnoreCert }) .then((resp) => handleJsonFetchResponse(url, resp)) .catch((err) => { console.log("error setting winsize", err); @@ -429,7 +434,7 @@ function mainPowerHandler(status: string) { const url = new URL(getBaseHostPort() + "/api/power-monitor"); const fetchHeaders = getFetchHeaders(); const body = { status: status }; - fetch(url, { method: "post", body: JSON.stringify(body), headers: fetchHeaders }) + fetch(url, { method: "post", body: JSON.stringify(body), headers: fetchHeaders, agent: httpsAgentIgnoreCert }) .then((resp) => handleJsonFetchResponse(url, resp)) .catch((err) => { console.log("error setting power monitor state", err); @@ -479,6 +484,14 @@ function calcBounds(clientData: ClientDataType): Electron.Rectangle { app.on("window-all-closed", () => { if (unamePlatform !== "darwin") app.quit(); }); +app.on("certificate-error", (event, webContents, url, error, certificate, callback) => { + if (url.startsWith(getBaseHostPort())) { + event.preventDefault(); + callback(true); + } else { + callback(false); + } +}); electron.ipcMain.on("toggle-developer-tools", (event) => { const window = getWindowForEvent(event); @@ -654,7 +667,7 @@ async function getClientDataPoll(loopNum: number): Promise { const url = new URL(getBaseHostPort() + "/api/get-client-data"); const fetchHeaders = getFetchHeaders(); - return fetch(url, { headers: fetchHeaders }) + return fetch(url, { headers: fetchHeaders, agent: httpsAgentIgnoreCert }) .then((resp) => handleJsonFetchResponse(url, resp)) .then((data) => { if (data == null) { @@ -770,7 +783,12 @@ function logActiveState() { const activeState = { fg: wasInFg, active: wasActive, open: true }; const url = new URL(getBaseHostPort() + "/api/log-active-state"); const fetchHeaders = getFetchHeaders(); - fetch(url, { method: "post", body: JSON.stringify(activeState), headers: fetchHeaders }) + fetch(url, { + method: "post", + body: JSON.stringify(activeState), + headers: fetchHeaders, + agent: httpsAgentIgnoreCert, + }) .then((resp) => handleJsonFetchResponse(url, resp)) .catch((err) => { console.log("error logging active state", err); diff --git a/wavesrv/cmd/main-server.go b/wavesrv/cmd/main-server.go index be98f4cb..d200bd52 100644 --- a/wavesrv/cmd/main-server.go +++ b/wavesrv/cmd/main-server.go @@ -5,6 +5,7 @@ package main import ( "context" + "crypto/tls" "encoding/base64" "encoding/json" "errors" @@ -14,6 +15,7 @@ import ( "io/fs" "log" "mime/multipart" + "net" "net/http" "os" "os/signal" @@ -1153,6 +1155,11 @@ func main() { if err != nil { log.Printf("[error] resetting screen focus: %v\n", err) } + tlsCert, err := waveenc.CreateSelfSignedLocalHostTlsCert() + if err != nil { + log.Printf("[error] creating self-signed tls cert: %v\n", err) + return + } log.Printf("PCLOUD_ENDPOINT=%s\n", pcloud.GetEndpoint()) startupActivityUpdate() @@ -1189,16 +1196,25 @@ func main() { if scbase.IsDevMode() { serverAddr = MainServerDevAddr } + tlsConfig := &tls.Config{} + tlsConfig.NextProtos = []string{"h2", "http/1.1"} + tlsConfig.Certificates = []tls.Certificate{*tlsCert} server := &http.Server{ Addr: serverAddr, ReadTimeout: HttpReadTimeout, WriteTimeout: HttpWriteTimeout, MaxHeaderBytes: HttpMaxHeaderBytes, Handler: http.TimeoutHandler(gr, HttpTimeoutDuration, "Timeout"), + TLSConfig: tlsConfig, } server.SetKeepAlivesEnabled(false) + netListener, err := net.Listen("tcp", serverAddr) + if err != nil { + log.Printf("[error] cannot listen on %s: %v\n", serverAddr, err) + return + } log.Printf("Running main server on %s\n", serverAddr) - err = server.ListenAndServe() + err = server.ServeTLS(netListener, "", "") if err != nil { log.Printf("ERROR: %v\n", err) } diff --git a/wavesrv/pkg/waveenc/cert.go b/wavesrv/pkg/waveenc/cert.go new file mode 100644 index 00000000..34ff6687 --- /dev/null +++ b/wavesrv/pkg/waveenc/cert.go @@ -0,0 +1,55 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package waveenc + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "math/big" + "net" + "time" +) + +func CreateSelfSignedLocalHostTlsCert() (*tls.Certificate, error) { + serialNumber, err := rand.Int(rand.Reader, big.NewInt(1000000000)) + if err != nil { + return nil, err + } + notBeforeTime, err := time.Parse("2006-01-02", "2020-01-01") + if err != nil { + return nil, err + } + notAfterTime, err := time.Parse("2006-01-02", "2030-01-01") + if err != nil { + return nil, err + } + certTemplate := &x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{CommonName: "127.0.0.1"}, + NotBefore: notBeforeTime, + NotAfter: notAfterTime, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + DNSNames: []string{"localhost"}, + IsCA: true, + } + privateKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, err + } + publicKey := privateKey.Public() + derBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, publicKey, privateKey) + if err != nil { + return nil, err + } + var tlsCert tls.Certificate + tlsCert.Certificate = append(tlsCert.Certificate, derBytes) + tlsCert.PrivateKey = privateKey + return &tlsCert, nil +}