Browse Source

Certificate, working server.

Frederic G. MARAND 2 years ago
parent
commit
bf9f248a98
6 changed files with 144 additions and 40 deletions
  1. 2 0
      .gitignore
  2. 3 1
      .idea/web_auth_demo.iml
  3. 28 39
      demo.go
  4. 4 0
      example.env
  5. 48 0
      server/middleware.go
  6. 59 0
      server/server.go

+ 2 - 0
.gitignore

@@ -1 +1,3 @@
 .idea/workspace.xml
+.env
+*.pem

+ 3 - 1
.idea/web_auth_demo.iml

@@ -2,7 +2,9 @@
 <module type="WEB_MODULE" version="4">
   <component name="Go" enabled="true" />
   <component name="NewModuleRootManager">
-    <content url="file://$MODULE_DIR$" />
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/.idea/runConfigurations" />
+    </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
   </component>

+ 28 - 39
demo.go

@@ -1,48 +1,37 @@
 package main
 
 import (
-	"crypto/sha256"
-	"crypto/subtle"
+	"log"
 	"net/http"
-)
+	"os"
+	"time"
 
-func basicAuth(next http.HandlerFunc) http.HandlerFunc {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		// Extract the username and password from the request Authorization header.
-		// If no Authentication header is present, or if the header value is
-		// invalid, then the 'ok' return value will be false.
-		username, password, ok := r.BasicAuth()
-		if ok {
-			// Calculate SHA-256 hashes for the provided and expected usernames
-			// and passwords in order to have constant length values.
-			usernameHash := sha256.Sum256([]byte(username))
-			passwordHash := sha256.Sum256([]byte(password))
-			expectedUsernameHash := sha256.Sum256([]byte("your expected username"))
-			expectedPasswordHash := sha256.Sum256([]byte("your expected password"))
+	"code.osinet.fr/fgm/web_auth_demo/server"
+)
 
-			// Use the subtle.ConstantTimeCompare() function to check if the
-			// provided username and password hashes equal the expected
-			// username and password hashes.
-			// ConstantTimeCompare will return 1 if the values are equal, or 0 otherwise.
-			// Importantly, we should do the work to evaluate both the username
-			// and password before checking the return values, to avoid leaking information.
-			usernameMatch := 1 == subtle.ConstantTimeCompare(usernameHash[:], expectedUsernameHash[:])
-			passwordMatch := 1 == subtle.ConstantTimeCompare(passwordHash[:], expectedPasswordHash[:])
+func main() {
+	app, err := server.New(
+		os.Getenv("AUTH_USERNAME"),
+		os.Getenv("AUTH_PASSWORD"),
+		os.Getenv("AUTH_CERT"),
+		os.Getenv("AUTH_CERT_KEY"),
+	)
+	if err != nil {
+		log.Fatal(err)
+	}
 
-			// If the username and password are correct, then call the next
-			// handler in the chain. Make sure to return afterwards, so that
-			// none of the code below is run.
-			if usernameMatch && passwordMatch {
-				next.ServeHTTP(w, r)
-				return
-			}
-		}
+	mux := http.NewServeMux()
+	mux.HandleFunc("/unprotected", app.UnprotectedHandler)
+	mux.HandleFunc("/protected", app.BasicAuth(app.ProtectedHandler))
 
-		// If the Authentication header is not present, is invalid, or the
-		// username or password is wrong, then set a WWW-Authenticate header
-		// to inform the client that we expect them to use basic authentication
-		// and send a 401 Unauthorized response.
-		w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
-		http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
-	})
+	srv := &http.Server{
+		Addr:         ":4000",
+		Handler:      mux,
+		ReadTimeout:  10 * time.Second,
+		WriteTimeout: 30 * time.Second,
+		IdleTimeout:  time.Minute,
+	}
+	log.Printf("Starting TLS server on %s", srv.Addr)
+	err = srv.ListenAndServeTLS(app.Cert, app.Key)
+	log.Fatal(err)
 }

+ 4 - 0
example.env

@@ -0,0 +1,4 @@
+AUTH_USERNAME=user
+AUTH_PASSWORD=pass
+AUTH_CERT=localhost.pem
+AUTH_CERT_KEY=localhost-key.pem

+ 48 - 0
server/middleware.go

@@ -0,0 +1,48 @@
+package server
+
+import (
+	"crypto/sha256"
+	"crypto/subtle"
+	"net/http"
+)
+
+func (app *Application) BasicAuth(next http.HandlerFunc) http.HandlerFunc {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// Extract the username and password from the request Authorization header.
+		// If no Authentication header is present, or if the header value is
+		// invalid, then the 'ok' return value will be false.
+		username, password, ok := r.BasicAuth()
+		if ok {
+			// Calculate SHA-256 hashes for the provided and expected usernames
+			// and passwords in order to have constant length values.
+			actualUsernameHash := sha256.Sum256([]byte(username))
+			actualPasswordHash := sha256.Sum256([]byte(password))
+			expectedUsernameHash := sha256.Sum256([]byte(app.Username))
+			expectedPasswordHash := sha256.Sum256([]byte(app.Password))
+
+			// Use the subtle.ConstantTimeCompare() function to check if the
+			// provided username and password hashes equal the expected
+			// username and password hashes.
+			// ConstantTimeCompare will return 1 if the values are equal, or 0 otherwise.
+			// Importantly, we should do the work to evaluate both the username
+			// and password before checking the return values, to avoid leaking information.
+			usernameMatch := 1 == subtle.ConstantTimeCompare(actualUsernameHash[:], expectedUsernameHash[:])
+			passwordMatch := 1 == subtle.ConstantTimeCompare(actualPasswordHash[:], expectedPasswordHash[:])
+
+			// If the username and password are correct, then call the next
+			// handler in the chain. Make sure to return afterwards, so that
+			// none of the code below is run.
+			if usernameMatch && passwordMatch {
+				next.ServeHTTP(w, r)
+				return
+			}
+		}
+
+		// If the Authentication header is not present, is invalid, or the
+		// username or password is wrong, then set a WWW-Authenticate header
+		// to inform the client that we expect them to use basic authentication
+		// and send a 401 Unauthorized response.
+		w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
+		http.Error(w, "Please provide an authorization", http.StatusUnauthorized)
+	})
+}

+ 59 - 0
server/server.go

@@ -0,0 +1,59 @@
+// Package server is an adaptation of the basic Auth demo by Alex Edwards
+// on https://www.alexedwards.net/blog/basic-authentication-in-go
+// where it is published under an MIT license.
+package server
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"os"
+)
+
+type Auth struct {
+	Username, Password string
+}
+
+type TLS struct {
+	Cert, Key string
+}
+
+type Application struct {
+	Auth
+	TLS
+}
+
+func (app *Application) UnprotectedHandler(w http.ResponseWriter, r *http.Request) {
+	fmt.Fprintln(w, "this is the unprotected handler")
+}
+
+func (app *Application) ProtectedHandler(w http.ResponseWriter, r *http.Request) {
+	fmt.Fprintln(w, "this is the protected handler")
+}
+
+func New(user, pass, cert, ckey string) (*Application, error) {
+	if user == "" {
+		return nil, errors.New("invalid username: may not be empty")
+	}
+	if pass == "" {
+		return nil, errors.New("invalid password: may not be empty")
+	}
+	if cert == "" {
+		return nil, errors.New("invalid certificate path: may not be empty")
+	}
+	if _, err := os.ReadFile(cert); err != nil {
+		return nil, fmt.Errorf("cannot read certificate file %s: %w", cert, err)
+	}
+	if ckey == "" {
+		return nil, errors.New("invalid certificate key path: may not be empty")
+	}
+	if _, err := os.ReadFile(ckey); err != nil {
+		return nil, fmt.Errorf("cannot read certificate key file %s: %w", cert, err)
+	}
+
+	app := &Application{
+		Auth{Username: user, Password: pass},
+		TLS{Cert: cert, Key: ckey},
+	}
+	return app, nil
+}