diff --git a/go.mod b/go.mod index 31715cf..a13df7d 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/urfave/cli/v2 v2.25.7 github.com/yudai/hcl v0.0.0-20151013225006-5fa2393b3552 + golang.org/x/crypto v0.14.0 ) require ( @@ -30,7 +31,6 @@ require ( github.com/rs/xid v1.5.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/crypto v0.14.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.14.0 // indirect diff --git a/main.go b/main.go index 5bd47c9..af3fb21 100644 --- a/main.go +++ b/main.go @@ -46,14 +46,14 @@ func main() { app.Flags = append( cliFlags, &cli.StringFlag{ - Name: "config", - Value: "~/.tty2web", - Usage: "Config file path", + Name: "config", + Value: "~/.tty2web", + Usage: "Config file path", EnvVars: []string{"TTY2WEB_CONFIG"}, }, &cli.BoolFlag{ - Name: "help", - Usage: "Displays help", + Name: "help", + Usage: "Displays help", }, ) @@ -71,6 +71,7 @@ func main() { utils.ApplyFlags(cliFlags, flagMappings, c, appOptions, backendOptions) appOptions.EnableBasicAuth = c.IsSet("credential") + appOptions.CredentialBcrypt = c.IsSet("credential-bcrypt") appOptions.EnableTLSClientAuth = c.IsSet("tls-ca-crt") err = appOptions.Validate() @@ -79,28 +80,28 @@ func main() { exit(err, 6) } - if appOptions.Dns!="" { + if appOptions.Dns != "" { if appOptions.DnsKey == "" { - appOptions.DnsKey=GenerateKey() + appOptions.DnsKey = GenerateKey() log.Printf("No password specified, generated following (recheck if same on both sides): %s", appOptions.DnsKey) } if len(appOptions.DnsKey) != 64 { fmt.Fprintf(os.Stderr, "Specified key of incorrect size for DNS (should be 64 in hex)\n") os.Exit(1) } - if appOptions.DnsListen!="" { + if appOptions.DnsListen != "" { go func() { - log.Fatal(ServeDNS (appOptions.DnsListen,appOptions.Dns, appOptions.Server, appOptions.DnsKey, appOptions.DnsDelay)) + log.Fatal(ServeDNS(appOptions.DnsListen, appOptions.Dns, appOptions.Server, appOptions.DnsKey, appOptions.DnsDelay)) }() wait4Signals() return nil } } - if appOptions.Listen!="" { + if appOptions.Listen != "" { log.Printf("Listening for reverse connection %s", appOptions.Listen) go func() { - log.Fatal(listenForAgents(appOptions.Verbose, appOptions.AgentTLS, appOptions.Listen, appOptions.Server, appOptions.ListenCert, appOptions.Password)) + log.Fatal(listenForAgents(appOptions.Verbose, appOptions.AgentTLS, appOptions.Listen, appOptions.Server, appOptions.ListenCert, appOptions.Password)) }() wait4Signals() return nil @@ -137,7 +138,6 @@ func main() { exit(err, 5) } - log.Printf("tty2web is starting with command: %s", strings.Join(args.Slice(), " ")) ctx, cancel := context.WithCancel(context.Background()) @@ -170,10 +170,10 @@ func wait4Signals() { c := make(chan os.Signal) signal.Notify(c, os.Interrupt) select { - case sig := <-c: - fmt.Printf("Got %s signal. Aborting...\n", sig) - os.Exit(1) - } + case sig := <-c: + fmt.Printf("Got %s signal. Aborting...\n", sig) + os.Exit(1) + } } func waitSignals(errs chan error, cancel context.CancelFunc, gracefullCancel context.CancelFunc) error { diff --git a/server/middleware.go b/server/middleware.go index 11300db..5c66088 100644 --- a/server/middleware.go +++ b/server/middleware.go @@ -1,10 +1,13 @@ package server import ( + "bytes" "encoding/base64" "log" "net/http" "strings" + + "golang.org/x/crypto/bcrypt" ) func (server *Server) wrapLogger(handler http.Handler) http.Handler { @@ -39,10 +42,38 @@ func (server *Server) wrapBasicAuth(handler http.Handler, credential string) htt return } - if credential != string(payload) { - w.Header().Set("WWW-Authenticate", `Basic realm="tty2web"`) - http.Error(w, "authorization failed", http.StatusUnauthorized) - return + if !server.options.CredentialBcrypt { + if credential != string(payload) { + w.Header().Set("WWW-Authenticate", `Basic realm="tty2web"`) + http.Error(w, "authorization failed", http.StatusUnauthorized) + return + } + } else { + credentialParts := strings.SplitN(credential, ":", 2) + if len(credentialParts) != 2 { + log.Printf("Invalid credential format on server") + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + payloadParts := bytes.SplitN(payload, []byte(":"), 2) + if len(payloadParts) != 2 { + w.Header().Set("WWW-Authenticate", `Basic realm="tty2web"`) + http.Error(w, "authorization failed", http.StatusUnauthorized) + return + } + + if credentialParts[0] != string(payloadParts[0]) { + w.Header().Set("WWW-Authenticate", `Basic realm="tty2web"`) + http.Error(w, "authorization failed", http.StatusUnauthorized) + return + } + + err := bcrypt.CompareHashAndPassword([]byte(credentialParts[1]), payloadParts[1]) + if err != nil { + w.Header().Set("WWW-Authenticate", `Basic realm="tty2web"`) + http.Error(w, "authorization failed", http.StatusUnauthorized) + return + } } log.Printf("Basic Authentication Succeeded: %s", r.RemoteAddr) diff --git a/server/options.go b/server/options.go index 6310305..904e008 100644 --- a/server/options.go +++ b/server/options.go @@ -10,6 +10,7 @@ type Options struct { PermitWrite bool `hcl:"permit_write" flagName:"permit-write" flagSName:"w" flagDescribe:"Permit clients to write to the TTY (BE CAREFUL)" default:"false"` EnableBasicAuth bool `hcl:"enable_basic_auth" default:"false"` Credential string `hcl:"credential" flagName:"credential" flagSName:"c" flagDescribe:"Credential for Basic Authentication (ex: user:pass, default disabled)" default:""` + CredentialBcrypt bool `hcl:"credential_bcrypt" flagName:"credential-bcrypt" flagDescribe:"Treat credential as a bcrypt hash" default:"false"` EnableRandomUrl bool `hcl:"enable_random_url" flagName:"random-url" flagSName:"r" flagDescribe:"Add a random string to the URL" default:"false"` EnableWebGL bool `hcl:"enable_webgl" flagName:"enable-webgl" flagDescribe:"Enable WebGL renderer" default:"true"` All bool `hcl:"all" flagName:"all" flagDescribe:"Turn on all features: download /, upload /, api, regeorg, ..." default:"false"`