diff options
| author | adnano <[email protected]> | 2020-10-12 16:34:52 -0400 |
|---|---|---|
| committer | adnano <[email protected]> | 2020-10-12 16:34:59 -0400 |
| commit | a33a5be0634cbbca6c6f8eb2fd53bceb014fed02 (patch) | |
| tree | a23fdfdab8807baa8a604023f25d729f3edaabf1 | |
| parent | Use a map for registering server handlers (diff) | |
| download | go-gemini-a33a5be0634cbbca6c6f8eb2fd53bceb014fed02.tar.xz go-gemini-a33a5be0634cbbca6c6f8eb2fd53bceb014fed02.zip | |
Update documentation
| -rw-r--r-- | README.md | 106 | ||||
| -rw-r--r-- | cert.go | 1 | ||||
| -rw-r--r-- | client.go | 4 | ||||
| -rw-r--r-- | doc.go | 80 | ||||
| -rw-r--r-- | examples/auth.go (renamed from examples/auth/auth.go) | 2 | ||||
| -rw-r--r-- | examples/cert.go (renamed from examples/cert/cert.go) | 2 | ||||
| -rw-r--r-- | examples/client.go (renamed from examples/client/client.go) | 4 | ||||
| -rw-r--r-- | examples/server.go (renamed from examples/server/server.go) | 16 | ||||
| -rw-r--r-- | gemini.go | 3 |
9 files changed, 100 insertions, 118 deletions
@@ -2,113 +2,13 @@ [](https://godoc.org/git.sr.ht/~adnano/gmi) -Package `gmi` implements the [Gemini protocol](https://gemini.circumlunar.space) -in Go. +Package `gmi` implements the [Gemini protocol](https://gemini.circumlunar.space) in Go. -It aims to provide an API similar to that of `net/http` to make it easy to -develop Gemini clients and servers. +It aims to provide an API similar to that of `net/http` to make it easy to develop Gemini clients and servers. ## Examples There are a few examples provided in the `examples` directory. -Some examples might require you to generate TLS certificates. - To run the examples: - go run -tags=example ./examples/server - -## Overview - -A quick overview of the Gemini protocol: - -1. Client opens connection -2. Server accepts connection -3. Client and server complete a TLS handshake -4. Client validates server certificate -5. Client sends request -6. Server sends response header -7. Server sends response body (only for successful responses) -8. Server closes connection -9. Client handles response - -The way this is implemented in this package is like so: - -1. Client makes a request with `NewRequest`. The client then sends the request - with `(*Client).Send(*Request) (*Response, error)`. The client then determines whether - to trust the certificate (see [Trust On First Use](#trust-on-first-use)). -2. Server recieves the request and constructs a response. - The server calls the `Serve(*ResponseWriter, *Request)` method on the - `Handler` field. The handler writes the response. The server then closes - the connection. -3. Client recieves the response as a `*Response`. The client then handles the - response. - -## Trust On First Use - -`gmi` makes it easy to implement Trust On First Use in your clients. - -The default client loads known hosts from `$XDG_DATA_HOME/gemini/known_hosts`. -If that is all you need, you can simply use the top-level `Send` function: - -```go -// Send uses the default client, which will load the default list of known hosts. -req := gmi.NewRequest("gemini://example.com") -gmi.Send(req) -``` - -Clients can also load their own list of known hosts: - -```go -client := &gmi.Client{} -if err := client.KnownHosts.LoadFrom("path/to/my/known_hosts"); err != nil { - log.Fatal(err) -} -``` - -Clients can then specify how to trust certificates in the `TrustCertificate` -field: - -```go -client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gmi.KnownHosts) error { - // If the certificate is in the known hosts list, allow the connection - return knownHosts.Lookup(hostname, cert) -} -``` - -Advanced clients can prompt the user for what to do when encountering an unknown -certificate. See `examples/client` for an example. - -## Client Authentication - -Gemini takes advantage of client certificates for authentication. - -If a server responds with `StatusCertificateRequired`, clients will generate a -certificate for the site and resend the request with the provided certificate. -The default client handles this for you. Other clients must specify the field -`GetCertificate`: - -```go -// GetCertificate is called when a server requests a certificate. -// The returned certificate, if not nil, will be used when resending the request. -client.GetCertificate = func(hostname string, store gmi.CertificateStore) *tls.Certificate { - // If the certificate is in the store, return it - if cert, ok := store[hostname]; ok { - return cert - } - // Otherwise, generate a certificate - duration := time.Hour - cert, err := gmi.NewCertificate(hostname, duration) - if err != nil { - return nil - } - // Store and return the certificate - store[hostname] = &cert - return &cert -} -``` - -Servers can then authenticate their clients with the fingerprint of their -certificates. - -See `examples/auth` for an example server which authenticates its users with -username and password, and uses their client certificate to remember sessions. + go run ./examples/server.go @@ -16,6 +16,7 @@ import ( ) // CertificateStore maps hostnames to certificates. +// The zero value of CertificateStore is an empty store ready to use. type CertificateStore struct { store map[string]tls.Certificate } @@ -198,7 +198,7 @@ type Client struct { // The returned certificate will be used when sending the request again. // If the certificate is nil, the request will not be sent again and // the response will be returned. - GetCertificate func(hostname string, store CertificateStore) *tls.Certificate + GetCertificate func(hostname string, store *CertificateStore) *tls.Certificate // TrustCertificate, if not nil, will be called to determine whether the // client should trust the given certificate. @@ -279,7 +279,7 @@ func (c *Client) Send(req *Request) (*Response, error) { return resp, nil } if c.GetCertificate != nil { - if cert := c.GetCertificate(req.Hostname(), c.CertificateStore); cert != nil { + if cert := c.GetCertificate(req.Hostname(), &c.CertificateStore); cert != nil { req.Certificate = cert return c.Send(req) } @@ -0,0 +1,80 @@ +/* +Package gmi implements the Gemini protocol. + +Send makes a Gemini request: + + req := gmi.NewRequest("gemini://example.com") + err := gmi.Send(req) + if err != nil { + // handle error + } + +For control over client behavior, create a Client: + + var client gmi.Client + err := client.Send(req) + if err != nil { + // handle error + } + +The default client loads known hosts from "$XDG_DATA_HOME/gemini/known_hosts". +Custom clients can load their own list of known hosts: + + err := client.KnownHosts.LoadFrom("path/to/my/known_hosts") + if err != nil { + // handle error + } + +Clients can control when to trust certificates with TrustCertificate: + + client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gmi.KnownHosts) error { + return knownHosts.Lookup(hostname, cert) + } + +If a server responds with StatusCertificateRequired, the default client will generate a certificate and resend the request with it. Custom clients can specify GetCertificate: + + 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 + 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 + } + +Server is a Gemini server. + + var server gmi.Server + +Servers must be configured with certificates: + + var server gmi.Server + server.CertificateStore.Load("/var/lib/gemini/certs") + +Servers can accept requests for multiple hosts and schemes: + + server.HandleFunc("example.com", func(rw *gmi.ResponseWriter, req *gmi.Request) { + fmt.Fprint(rw, "Welcome to example.com") + }) + server.HandleFunc("example.org", func(rw *gmi.ResponseWriter, req *gmi.Request) { + fmt.Fprint(rw, "Welcome to example.org") + }) + server.HandleSchemeFunc("http", "example.net", func(rw *gmi.ResponseWriter, req *gmi.Request) { + fmt.Fprint(rw, "Proxied content from example.net") + }) + +To start the server, call ListenAndServe: + + err := server.ListenAndServe() + if err != nil { + // handle error + } +*/ +package gmi diff --git a/examples/auth/auth.go b/examples/auth.go index 9a7c0bd..9f3045f 100644 --- a/examples/auth/auth.go +++ b/examples/auth.go @@ -1,4 +1,4 @@ -// +build example +// +build ignore package main diff --git a/examples/cert/cert.go b/examples/cert.go index 4431319..4c08578 100644 --- a/examples/cert/cert.go +++ b/examples/cert.go @@ -1,4 +1,4 @@ -// +build example +// +build ignore package main diff --git a/examples/client/client.go b/examples/client.go index ee65c9d..d2819ec 100644 --- a/examples/client/client.go +++ b/examples/client.go @@ -1,4 +1,4 @@ -// +build example +// +build ignore package main @@ -46,7 +46,7 @@ func init() { } return err } - client.GetCertificate = func(hostname string, store gmi.CertificateStore) *tls.Certificate { + 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 diff --git a/examples/server/server.go b/examples/server.go index 01890ba..a4ed808 100644 --- a/examples/server/server.go +++ b/examples/server.go @@ -1,4 +1,4 @@ -// +build example +// +build ignore package main @@ -9,14 +9,16 @@ import ( ) func main() { - mux := &gmi.ServeMux{} + 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 := gmi.Server{} - if err := server.CertificateStore.Load("/var/lib/gemini/certs"); err != nil { + server.Handle("localhost", &mux) + if err := server.ListenAndServe(); err != nil { log.Fatal(err) } - log.Print(server.CertificateStore) - server.Handle("localhost", mux) - server.ListenAndServe() } @@ -1,4 +1,3 @@ -// Package gmi implements the Gemini protocol package gmi import ( @@ -56,7 +55,7 @@ func init() { setupDefaultClientOnce.Do(setupDefaultClient) return knownHosts.Lookup(hostname, cert) } - DefaultClient.GetCertificate = func(hostname string, store CertificateStore) *tls.Certificate { + DefaultClient.GetCertificate = func(hostname string, store *CertificateStore) *tls.Certificate { // If the certificate is in the store, return it if cert, err := store.Lookup(hostname); err == nil { return cert |