From a33a5be0634cbbca6c6f8eb2fd53bceb014fed02 Mon Sep 17 00:00:00 2001 From: adnano Date: Mon, 12 Oct 2020 16:34:52 -0400 Subject: Update documentation --- examples/auth.go | 132 +++++++++++++++++++++++++++++++++++++ examples/auth/auth.go | 132 ------------------------------------- examples/cert.go | 24 +++++++ examples/cert/cert.go | 24 ------- examples/client.go | 165 ++++++++++++++++++++++++++++++++++++++++++++++ examples/client/client.go | 165 ---------------------------------------------- examples/server.go | 24 +++++++ examples/server/server.go | 22 ------- 8 files changed, 345 insertions(+), 343 deletions(-) create mode 100644 examples/auth.go delete mode 100644 examples/auth/auth.go create mode 100644 examples/cert.go delete mode 100644 examples/cert/cert.go create mode 100644 examples/client.go delete mode 100644 examples/client/client.go create mode 100644 examples/server.go delete mode 100644 examples/server/server.go (limited to 'examples') diff --git a/examples/auth.go b/examples/auth.go new file mode 100644 index 0000000..9f3045f --- /dev/null +++ b/examples/auth.go @@ -0,0 +1,132 @@ +// +build ignore + +package main + +import ( + "crypto/x509" + "fmt" + "log" + + "git.sr.ht/~adnano/gmi" +) + +type user struct { + password string // TODO: use hashes + admin bool +} + +type session struct { + username string + authorized bool // whether or not the password was supplied +} + +var ( + // Map of usernames to user data + logins = map[string]user{ + "admin": {"p@ssw0rd", true}, // NOTE: These are bad passwords! + "user1": {"password1", false}, + "user2": {"password2", false}, + } + + // Map of certificate fingerprints to sessions + sessions = map[string]*session{} +) + +func main() { + handler := &gmi.ServeMux{} + handler.HandleFunc("/", welcome) + handler.HandleFunc("/login", login) + handler.HandleFunc("/login/password", loginPassword) + handler.HandleFunc("/profile", profile) + handler.HandleFunc("/admin", admin) + handler.HandleFunc("/logout", logout) + + server := &gmi.Server{} + if err := server.CertificateStore.Load("/var/lib/gemini/certs"); err != nil { + log.Fatal(err) + } + server.Handle("localhost", handler) + + if err := server.ListenAndServe(); err != nil { + log.Fatal(err) + } +} + +func getSession(crt *x509.Certificate) (*session, bool) { + fingerprint := gmi.Fingerprint(crt) + session, ok := sessions[fingerprint] + return session, ok +} + +func welcome(rw *gmi.ResponseWriter, req *gmi.Request) { + rw.Write([]byte("Welcome to this example.\n=> /login Login\n")) +} + +func login(rw *gmi.ResponseWriter, req *gmi.Request) { + gmi.WithCertificate(rw, req, func(cert *x509.Certificate) { + gmi.WithInput(rw, req, "Username", func(username string) { + fingerprint := gmi.Fingerprint(cert) + sessions[fingerprint] = &session{ + username: username, + } + gmi.Redirect(rw, req, "/login/password") + }) + }) +} + +func loginPassword(rw *gmi.ResponseWriter, req *gmi.Request) { + gmi.WithCertificate(rw, req, func(cert *x509.Certificate) { + session, ok := getSession(cert) + if !ok { + gmi.CertificateNotAuthorized(rw, req) + return + } + + gmi.WithSensitiveInput(rw, req, "Password", func(password string) { + expected := logins[session.username].password + if password == expected { + session.authorized = true + gmi.Redirect(rw, req, "/profile") + } else { + gmi.SensitiveInput(rw, req, "Wrong password. Try again") + } + }) + }) +} + +func logout(rw *gmi.ResponseWriter, req *gmi.Request) { + gmi.WithCertificate(rw, req, func(cert *x509.Certificate) { + fingerprint := gmi.Fingerprint(cert) + delete(sessions, fingerprint) + }) + rw.Write([]byte("Successfully logged out.\n")) +} + +func profile(rw *gmi.ResponseWriter, req *gmi.Request) { + gmi.WithCertificate(rw, req, func(cert *x509.Certificate) { + session, ok := getSession(cert) + if !ok { + gmi.CertificateNotAuthorized(rw, req) + return + } + user := logins[session.username] + profile := fmt.Sprintf("Username: %s\nAdmin: %t\n=> /logout Logout", session.username, user.admin) + rw.Write([]byte(profile)) + }) +} + +func admin(rw *gmi.ResponseWriter, req *gmi.Request) { + gmi.WithCertificate(rw, req, func(cert *x509.Certificate) { + session, ok := getSession(cert) + if !ok { + gmi.CertificateNotAuthorized(rw, req) + return + } + user := logins[session.username] + if !user.admin { + gmi.CertificateNotAuthorized(rw, req) + return + } + rw.Write([]byte("Welcome to the admin portal.\n")) + }) +} diff --git a/examples/auth/auth.go b/examples/auth/auth.go deleted file mode 100644 index 9a7c0bd..0000000 --- a/examples/auth/auth.go +++ /dev/null @@ -1,132 +0,0 @@ -// +build example - -package main - -import ( - "crypto/x509" - "fmt" - "log" - - "git.sr.ht/~adnano/gmi" -) - -type user struct { - password string // TODO: use hashes - admin bool -} - -type session struct { - username string - authorized bool // whether or not the password was supplied -} - -var ( - // Map of usernames to user data - logins = map[string]user{ - "admin": {"p@ssw0rd", true}, // NOTE: These are bad passwords! - "user1": {"password1", false}, - "user2": {"password2", false}, - } - - // Map of certificate fingerprints to sessions - sessions = map[string]*session{} -) - -func main() { - handler := &gmi.ServeMux{} - handler.HandleFunc("/", welcome) - handler.HandleFunc("/login", login) - handler.HandleFunc("/login/password", loginPassword) - handler.HandleFunc("/profile", profile) - handler.HandleFunc("/admin", admin) - handler.HandleFunc("/logout", logout) - - server := &gmi.Server{} - if err := server.CertificateStore.Load("/var/lib/gemini/certs"); err != nil { - log.Fatal(err) - } - server.Handle("localhost", handler) - - if err := server.ListenAndServe(); err != nil { - log.Fatal(err) - } -} - -func getSession(crt *x509.Certificate) (*session, bool) { - fingerprint := gmi.Fingerprint(crt) - session, ok := sessions[fingerprint] - return session, ok -} - -func welcome(rw *gmi.ResponseWriter, req *gmi.Request) { - rw.Write([]byte("Welcome to this example.\n=> /login Login\n")) -} - -func login(rw *gmi.ResponseWriter, req *gmi.Request) { - gmi.WithCertificate(rw, req, func(cert *x509.Certificate) { - gmi.WithInput(rw, req, "Username", func(username string) { - fingerprint := gmi.Fingerprint(cert) - sessions[fingerprint] = &session{ - username: username, - } - gmi.Redirect(rw, req, "/login/password") - }) - }) -} - -func loginPassword(rw *gmi.ResponseWriter, req *gmi.Request) { - gmi.WithCertificate(rw, req, func(cert *x509.Certificate) { - session, ok := getSession(cert) - if !ok { - gmi.CertificateNotAuthorized(rw, req) - return - } - - gmi.WithSensitiveInput(rw, req, "Password", func(password string) { - expected := logins[session.username].password - if password == expected { - session.authorized = true - gmi.Redirect(rw, req, "/profile") - } else { - gmi.SensitiveInput(rw, req, "Wrong password. Try again") - } - }) - }) -} - -func logout(rw *gmi.ResponseWriter, req *gmi.Request) { - gmi.WithCertificate(rw, req, func(cert *x509.Certificate) { - fingerprint := gmi.Fingerprint(cert) - delete(sessions, fingerprint) - }) - rw.Write([]byte("Successfully logged out.\n")) -} - -func profile(rw *gmi.ResponseWriter, req *gmi.Request) { - gmi.WithCertificate(rw, req, func(cert *x509.Certificate) { - session, ok := getSession(cert) - if !ok { - gmi.CertificateNotAuthorized(rw, req) - return - } - user := logins[session.username] - profile := fmt.Sprintf("Username: %s\nAdmin: %t\n=> /logout Logout", session.username, user.admin) - rw.Write([]byte(profile)) - }) -} - -func admin(rw *gmi.ResponseWriter, req *gmi.Request) { - gmi.WithCertificate(rw, req, func(cert *x509.Certificate) { - session, ok := getSession(cert) - if !ok { - gmi.CertificateNotAuthorized(rw, req) - return - } - user := logins[session.username] - if !user.admin { - gmi.CertificateNotAuthorized(rw, req) - return - } - rw.Write([]byte("Welcome to the admin portal.\n")) - }) -} diff --git a/examples/cert.go b/examples/cert.go new file mode 100644 index 0000000..4c08578 --- /dev/null +++ b/examples/cert.go @@ -0,0 +1,24 @@ +// +build ignore + +package main + +import ( + "log" + "time" + + "git.sr.ht/~adnano/gmi" +) + +func main() { + host := "localhost" + + duration := 365 * 24 * time.Hour + crt, key, err := gmi.NewRawCertificate(host, duration) + if err != nil { + log.Fatal(err) + } + + if err := gmi.WriteX509KeyPair(host, crt, key); err != nil { + log.Fatal(err) + } +} diff --git a/examples/cert/cert.go b/examples/cert/cert.go deleted file mode 100644 index 4431319..0000000 --- a/examples/cert/cert.go +++ /dev/null @@ -1,24 +0,0 @@ -// +build example - -package main - -import ( - "log" - "time" - - "git.sr.ht/~adnano/gmi" -) - -func main() { - host := "localhost" - - duration := 365 * 24 * time.Hour - crt, key, err := gmi.NewRawCertificate(host, duration) - if err != nil { - log.Fatal(err) - } - - if err := gmi.WriteX509KeyPair(host, crt, key); err != nil { - log.Fatal(err) - } -} diff --git a/examples/client.go b/examples/client.go new file mode 100644 index 0000000..d2819ec --- /dev/null +++ b/examples/client.go @@ -0,0 +1,165 @@ +// +build ignore + +package main + +import ( + "bufio" + "crypto/tls" + "crypto/x509" + "fmt" + "os" + "time" + + "git.sr.ht/~adnano/gmi" +) + +var ( + scanner = bufio.NewScanner(os.Stdin) + client = &gmi.Client{} +) + +func init() { + // Initialize the client + client.KnownHosts.Load() // Load known hosts + client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gmi.KnownHosts) error { + err := knownHosts.Lookup(hostname, cert) + if err != nil { + switch err { + case gmi.ErrCertificateNotTrusted: + // Alert the user that the certificate is not trusted + fmt.Printf("Warning: Certificate for %s is not trusted!\n", hostname) + fmt.Println("This could indicate a Man-in-the-Middle attack.") + case gmi.ErrUnknownCertificate: + // Prompt the user to trust the certificate + trust := trustCertificate(cert) + switch trust { + case trustOnce: + // Temporarily trust the certificate + knownHosts.AddTemporary(hostname, cert) + return nil + case trustAlways: + // Add the certificate to the known hosts file + knownHosts.Add(hostname, cert) + return nil + } + } + } + return err + } + client.GetCertificate = func(hostname string, store *gmi.CertificateStore) *tls.Certificate { + // If the certificate is in the store, return it + if cert, err := store.Lookup(hostname); err == nil { + return cert + } + // Otherwise, generate a certificate + fmt.Println("Generating client certificate for", hostname) + duration := time.Hour + cert, err := gmi.NewCertificate(hostname, duration) + if err != nil { + return nil + } + // Store and return the certificate + store.Add(hostname, cert) + return &cert + } +} + +// sendRequest sends a request to the given URL. +func sendRequest(req *gmi.Request) error { + resp, err := client.Send(req) + if err != nil { + return err + } + + // TODO: More fine-grained analysis of the status code. + switch resp.Status / 10 { + case gmi.StatusClassInput: + fmt.Printf("%s: ", resp.Meta) + scanner.Scan() + req.URL.RawQuery = scanner.Text() + return sendRequest(req) + case gmi.StatusClassSuccess: + fmt.Print(string(resp.Body)) + return nil + case gmi.StatusClassRedirect: + fmt.Println("Redirecting to", resp.Meta) + // Make the request to the same host + red, err := gmi.NewRequestTo(resp.Meta, req.Host) + if err != nil { + return err + } + // Handle relative redirects + red.URL = req.URL.ResolveReference(red.URL) + return sendRequest(red) + case gmi.StatusClassTemporaryFailure: + return fmt.Errorf("Temporary failure: %s", resp.Meta) + case gmi.StatusClassPermanentFailure: + return fmt.Errorf("Permanent failure: %s", resp.Meta) + case gmi.StatusClassCertificateRequired: + // Note that this should not happen unless the server responds with + // CertificateRequired even after we send a certificate. + // CertificateNotAuthorized and CertificateNotValid are handled here. + return fmt.Errorf("Certificate required: %s", resp.Meta) + } + panic("unreachable") +} + +type trust int + +const ( + trustAbort trust = iota + trustOnce + trustAlways +) + +const trustPrompt = `The certificate offered by this server is of unknown trust. Its fingerprint is: +%s + +If you knew the fingerprint to expect in advance, verify that this matches. +Otherwise, this should be safe to trust. + +[t]rust always; trust [o]nce; [a]bort +=> ` + +func trustCertificate(cert *x509.Certificate) trust { + fmt.Printf(trustPrompt, gmi.Fingerprint(cert)) + scanner.Scan() + switch scanner.Text() { + case "t": + return trustAlways + case "o": + return trustOnce + default: + return trustAbort + } +} + +func main() { + if len(os.Args) < 2 { + fmt.Printf("usage: %s gemini://...", os.Args[0]) + os.Exit(1) + } + + var host string + if len(os.Args) >= 3 { + host = os.Args[2] + } + + url := os.Args[1] + var req *gmi.Request + var err error + if host != "" { + req, err = gmi.NewRequestTo(url, host) + } else { + req, err = gmi.NewRequest(url) + } + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := sendRequest(req); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/examples/client/client.go b/examples/client/client.go deleted file mode 100644 index ee65c9d..0000000 --- a/examples/client/client.go +++ /dev/null @@ -1,165 +0,0 @@ -// +build example - -package main - -import ( - "bufio" - "crypto/tls" - "crypto/x509" - "fmt" - "os" - "time" - - "git.sr.ht/~adnano/gmi" -) - -var ( - scanner = bufio.NewScanner(os.Stdin) - client = &gmi.Client{} -) - -func init() { - // Initialize the client - client.KnownHosts.Load() // Load known hosts - client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gmi.KnownHosts) error { - err := knownHosts.Lookup(hostname, cert) - if err != nil { - switch err { - case gmi.ErrCertificateNotTrusted: - // Alert the user that the certificate is not trusted - fmt.Printf("Warning: Certificate for %s is not trusted!\n", hostname) - fmt.Println("This could indicate a Man-in-the-Middle attack.") - case gmi.ErrUnknownCertificate: - // Prompt the user to trust the certificate - trust := trustCertificate(cert) - switch trust { - case trustOnce: - // Temporarily trust the certificate - knownHosts.AddTemporary(hostname, cert) - return nil - case trustAlways: - // Add the certificate to the known hosts file - knownHosts.Add(hostname, cert) - return nil - } - } - } - return err - } - client.GetCertificate = func(hostname string, store gmi.CertificateStore) *tls.Certificate { - // If the certificate is in the store, return it - if cert, err := store.Lookup(hostname); err == nil { - return cert - } - // Otherwise, generate a certificate - fmt.Println("Generating client certificate for", hostname) - duration := time.Hour - cert, err := gmi.NewCertificate(hostname, duration) - if err != nil { - return nil - } - // Store and return the certificate - store.Add(hostname, cert) - return &cert - } -} - -// sendRequest sends a request to the given URL. -func sendRequest(req *gmi.Request) error { - resp, err := client.Send(req) - if err != nil { - return err - } - - // TODO: More fine-grained analysis of the status code. - switch resp.Status / 10 { - case gmi.StatusClassInput: - fmt.Printf("%s: ", resp.Meta) - scanner.Scan() - req.URL.RawQuery = scanner.Text() - return sendRequest(req) - case gmi.StatusClassSuccess: - fmt.Print(string(resp.Body)) - return nil - case gmi.StatusClassRedirect: - fmt.Println("Redirecting to", resp.Meta) - // Make the request to the same host - red, err := gmi.NewRequestTo(resp.Meta, req.Host) - if err != nil { - return err - } - // Handle relative redirects - red.URL = req.URL.ResolveReference(red.URL) - return sendRequest(red) - case gmi.StatusClassTemporaryFailure: - return fmt.Errorf("Temporary failure: %s", resp.Meta) - case gmi.StatusClassPermanentFailure: - return fmt.Errorf("Permanent failure: %s", resp.Meta) - case gmi.StatusClassCertificateRequired: - // Note that this should not happen unless the server responds with - // CertificateRequired even after we send a certificate. - // CertificateNotAuthorized and CertificateNotValid are handled here. - return fmt.Errorf("Certificate required: %s", resp.Meta) - } - panic("unreachable") -} - -type trust int - -const ( - trustAbort trust = iota - trustOnce - trustAlways -) - -const trustPrompt = `The certificate offered by this server is of unknown trust. Its fingerprint is: -%s - -If you knew the fingerprint to expect in advance, verify that this matches. -Otherwise, this should be safe to trust. - -[t]rust always; trust [o]nce; [a]bort -=> ` - -func trustCertificate(cert *x509.Certificate) trust { - fmt.Printf(trustPrompt, gmi.Fingerprint(cert)) - scanner.Scan() - switch scanner.Text() { - case "t": - return trustAlways - case "o": - return trustOnce - default: - return trustAbort - } -} - -func main() { - if len(os.Args) < 2 { - fmt.Printf("usage: %s gemini://...", os.Args[0]) - os.Exit(1) - } - - var host string - if len(os.Args) >= 3 { - host = os.Args[2] - } - - url := os.Args[1] - var req *gmi.Request - var err error - if host != "" { - req, err = gmi.NewRequestTo(url, host) - } else { - req, err = gmi.NewRequest(url) - } - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - if err := sendRequest(req); err != nil { - fmt.Println(err) - os.Exit(1) - } -} diff --git a/examples/server.go b/examples/server.go new file mode 100644 index 0000000..a4ed808 --- /dev/null +++ b/examples/server.go @@ -0,0 +1,24 @@ +// +build ignore + +package main + +import ( + "log" + + "git.sr.ht/~adnano/gmi" +) + +func main() { + var server gmi.Server + if err := server.CertificateStore.Load("/var/lib/gemini/certs"); err != nil { + log.Fatal(err) + } + + var mux gmi.ServeMux + mux.Handle("/", gmi.FileServer(gmi.Dir("/var/www"))) + + server.Handle("localhost", &mux) + if err := server.ListenAndServe(); err != nil { + log.Fatal(err) + } +} diff --git a/examples/server/server.go b/examples/server/server.go deleted file mode 100644 index 01890ba..0000000 --- a/examples/server/server.go +++ /dev/null @@ -1,22 +0,0 @@ -// +build example - -package main - -import ( - "log" - - "git.sr.ht/~adnano/gmi" -) - -func main() { - mux := &gmi.ServeMux{} - mux.Handle("/", gmi.FileServer(gmi.Dir("/var/www"))) - - server := gmi.Server{} - if err := server.CertificateStore.Load("/var/lib/gemini/certs"); err != nil { - log.Fatal(err) - } - log.Print(server.CertificateStore) - server.Handle("localhost", mux) - server.ListenAndServe() -} -- cgit v1.2.3