aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoradnano <[email protected]>2020-09-27 19:45:48 -0400
committeradnano <[email protected]>2020-09-27 19:45:48 -0400
commit9f1a38a0ddb3402ca7664fca5ff0842ce2194084 (patch)
tree6ac2f29f208e5125ce1657caf36fdec2d2f4e1d5
parentUpdate README.md (diff)
downloadgo-gemini-9f1a38a0ddb3402ca7664fca5ff0842ce2194084.tar.xz
go-gemini-9f1a38a0ddb3402ca7664fca5ff0842ce2194084.zip
Polish example client
-rw-r--r--README.md31
-rw-r--r--client.go3
-rw-r--r--examples/auth/auth.go6
-rw-r--r--examples/client/client.go125
4 files changed, 77 insertions, 88 deletions
diff --git a/README.md b/README.md
index ca7b8b6..4847217 100644
--- a/README.md
+++ b/README.md
@@ -75,35 +75,8 @@ client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHos
}
```
-Advanced clients can prompt the user for what to do when encountering an unknown certificate:
-
-```go
-client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
- err := knownHosts.Lookup(cert)
- if err != nil {
- switch err {
- case gemini.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 gemini.ErrCertificateUnknown:
- // Prompt the user to trust the certificate
- if userTrustsCertificateTemporarily() {
- // Temporarily trust the certificate
- knownHosts.AddTemporary(hostname, cert)
- return nil
- } else if userTrustsCertificatePermanently() {
- // Add the certificate to the known hosts file
- knownHosts.Add(cert)
- return nil
- }
- }
- }
- return err
-}
-```
-
-See `examples/client` for an example client.
+Advanced clients can prompt the user for what to do when encountering an unknown
+certificate. See `examples/client` for an example.
## Client Authentication
diff --git a/client.go b/client.go
index de625d7..2914d41 100644
--- a/client.go
+++ b/client.go
@@ -201,6 +201,9 @@ func (c *Client) Send(req *Request) (*Response, error) {
return cert, nil
}
}
+ if req.Certificate == nil {
+ return &tls.Certificate{}, nil
+ }
return req.Certificate, nil
},
VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
diff --git a/examples/auth/auth.go b/examples/auth/auth.go
index 2827bec..f135c48 100644
--- a/examples/auth/auth.go
+++ b/examples/auth/auth.go
@@ -106,7 +106,7 @@ func loginPassword(rw *gemini.ResponseWriter, req *gemini.Request) {
session.authorized = true
rw.WriteHeader(gemini.StatusRedirectTemporary, "gemini://localhost/profile")
} else {
- rw.WriteHeader(gemini.StatusInput, "Wrong password. Please try again.\nPassword:")
+ rw.WriteHeader(gemini.StatusInput, "Wrong password. Please try again")
}
}
} else {
@@ -123,6 +123,10 @@ func logout(rw *gemini.ResponseWriter, req *gemini.Request) {
rw.Write([]byte("Successfully logged out.\n"))
}
+func badLogin(rw *gemini.ResponseWriter, req *gemini.Request) {
+
+}
+
func profile(rw *gemini.ResponseWriter, req *gemini.Request) {
if len(req.TLS.PeerCertificates) > 0 {
session, ok := getSession(req.TLS.PeerCertificates[0])
diff --git a/examples/client/client.go b/examples/client/client.go
index 52a1253..402e8f6 100644
--- a/examples/client/client.go
+++ b/examples/client/client.go
@@ -4,39 +4,39 @@ package main
import (
"bufio"
- "crypto/tls"
"crypto/x509"
"fmt"
- "log"
"os"
"git.sr.ht/~adnano/go-gemini"
)
var (
- client *gemini.Client
- cert tls.Certificate
+ scanner = bufio.NewScanner(os.Stdin)
+ client *gemini.Client
)
func init() {
+ // Initialize the client
client = &gemini.Client{}
- client.KnownHosts.Load()
-
+ client.KnownHosts.Load() // Load known hosts
client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
err := knownHosts.Lookup(hostname, cert)
if err != nil {
switch err {
case gemini.ErrCertificateNotTrusted:
// Alert the user that the certificate is not trusted
- fmt.Printf("Warning: certificate for %s is not trusted!\n", hostname)
+ fmt.Printf("Warning: Certificate for %s is not trusted!\n", hostname)
fmt.Println("This could indicate a Man-in-the-Middle attack.")
case gemini.ErrCertificateUnknown:
// Prompt the user to trust the certificate
- if userTrustsCertificateTemporarily() {
+ trust := trustCertificate(cert)
+ switch trust {
+ case trustOnce:
// Temporarily trust the certificate
knownHosts.AddTemporary(hostname, cert)
return nil
- } else if userTrustsCertificatePermanently() {
+ case trustAlways:
// Add the certificate to the known hosts file
knownHosts.Add(hostname, cert)
return nil
@@ -45,79 +45,88 @@ func init() {
}
return err
}
-
- client.GetCertificate = func(req *gemini.Request, store *gemini.CertificateStore) *tls.Certificate {
- return &cert
- }
-
- // Configure a client side certificate.
- // To generate a TLS key pair, run:
- //
- // go run -tags=example ../cert
- var err error
- cert, err = tls.LoadX509KeyPair("examples/client/localhost.crt", "examples/client/localhost.key")
- if err != nil {
- log.Fatal(err)
- }
}
-func makeRequest(url string) {
- req, err := gemini.NewRequest(url)
- if err != nil {
- log.Fatal(err)
- }
- req.Certificate = &cert
-
+// sendRequest sends a request to the given url.
+func sendRequest(req *gemini.Request) error {
resp, err := client.Send(req)
if err != nil {
- log.Fatal(err)
+ return err
}
- fmt.Println("Status code:", resp.Status)
- fmt.Println("Meta:", resp.Meta)
-
switch resp.Status / 10 {
case gemini.StatusClassInput:
- scanner := bufio.NewScanner(os.Stdin)
fmt.Printf("%s: ", resp.Meta)
scanner.Scan()
- query := scanner.Text()
- makeRequest(url + "?" + query)
- return
+ req.URL.RawQuery = scanner.Text()
+ return sendRequest(req)
case gemini.StatusClassSuccess:
- fmt.Print("Body:\n", string(resp.Body))
+ fmt.Print(string(resp.Body))
+ return nil
case gemini.StatusClassRedirect:
- log.Print("Redirecting to ", resp.Meta)
- makeRequest(resp.Meta)
- return
+ fmt.Println("Redirecting to ", resp.Meta)
+ req, err := gemini.NewRequest(resp.Meta)
+ if err != nil {
+ return err
+ }
+ return sendRequest(req)
case gemini.StatusClassTemporaryFailure:
- log.Fatal("Temporary failure")
+ return fmt.Errorf("Temporary failure: %s", resp.Meta)
case gemini.StatusClassPermanentFailure:
- log.Fatal("Permanent failure")
+ return fmt.Errorf("Permanent failure: %s", resp.Meta)
case gemini.StatusClassClientCertificateRequired:
- log.Fatal("Client certificate required")
+ fmt.Println("Generating client certificate for", req.Hostname())
+ return nil // TODO: Generate and store client certificate
default:
- log.Fatal("Protocol error")
+ return fmt.Errorf("Protocol error: Server sent an invalid response")
}
}
-func userTrustsCertificateTemporarily() bool {
- fmt.Print("Do you want to trust the certificate temporarily? (y/n) ")
- scanner := bufio.NewScanner(os.Stdin)
- scanner.Scan()
- return scanner.Text() == "y"
-}
+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
-func userTrustsCertificatePermanently() bool {
- fmt.Print("How about permanently? (y/n) ")
- scanner := bufio.NewScanner(os.Stdin)
+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, gemini.Fingerprint(cert))
scanner.Scan()
- return scanner.Text() == "y"
+ switch scanner.Text() {
+ case "t":
+ return trustAlways
+ case "o":
+ return trustOnce
+ default:
+ return trustAbort
+ }
}
func main() {
if len(os.Args) < 2 {
- log.Fatalf("usage: %s gemini://...", os.Args[0])
+ fmt.Println("usage: %s gemini://...", os.Args[0])
+ os.Exit(1)
+ }
+
+ url := os.Args[1]
+ req, err := gemini.NewRequest(url)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ if err := sendRequest(req); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
}
- makeRequest(os.Args[1])
}