go-gemini/README.md

105 lines
3.2 KiB
Markdown
Raw Normal View History

2020-09-21 19:48:42 +00:00
# go-gemini
2020-09-21 22:28:34 +00:00
[![GoDoc](https://godoc.org/git.sr.ht/~adnano/go-gemini?status.svg)](https://godoc.org/git.sr.ht/~adnano/go-gemini)
2020-09-22 02:09:50 +00:00
`go-gemini` implements the [Gemini protocol](https://gemini.circumlunar.space)
in Go.
2020-09-21 19:48:42 +00:00
2020-09-21 21:23:51 +00:00
It aims to provide an API similar to that of `net/http` to make it easy to
develop Gemini clients and servers.
2020-09-21 19:48:42 +00:00
2020-09-21 21:23:51 +00:00
## Examples
2020-09-21 19:48:42 +00:00
2020-09-21 21:23:51 +00:00
See `examples/client` and `examples/server` for an example client and server.
2020-09-21 19:48:42 +00:00
2020-09-21 21:23:51 +00:00
To run the examples:
2020-09-21 19:48:42 +00:00
2020-09-21 22:25:31 +00:00
go run -tags=example ./examples/server
2020-09-25 00:13:59 +00:00
## 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:
2020-09-25 22:53:20 +00:00
1. Client makes a request with `NewRequest`. The client then sends the request
2020-09-26 03:06:54 +00:00
with `(*Client).Send(*Request) (*Response, error)`. The client then determines whether
2020-09-27 20:21:56 +00:00
to trust the certificate (see [TOFU](#tofu)).
2020-09-25 00:13:59 +00:00
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.
2020-09-25 23:53:50 +00:00
3. Client recieves the response as a `*Response`. The client then handles the
response.
2020-09-26 03:06:54 +00:00
## TOFU
2020-09-26 17:59:24 +00:00
`go-gemini` makes it easy to implement Trust On First Use in your clients.
2020-09-27 20:21:56 +00:00
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
2020-09-27 20:31:41 +00:00
// Send uses the default client, which will load the default list of known hosts.
2020-09-27 20:21:56 +00:00
req := gemini.NewRequest("gemini://example.com")
gemini.Send(req)
```
Clients can also load their own list of known hosts:
2020-09-26 03:06:54 +00:00
```go
2020-09-26 17:59:24 +00:00
client := &Client{}
2020-09-27 20:21:56 +00:00
if err := client.KnownHosts.LoadFrom("path/to/my/known_hosts"); err != nil {
2020-09-26 17:59:24 +00:00
log.Fatal(err)
2020-09-26 03:06:54 +00:00
}
```
2020-09-26 17:59:24 +00:00
Clients can then specify how to trust certificates in the `TrustCertificate`
field:
```go
2020-09-27 20:21:56 +00:00
client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
2020-09-26 17:59:24 +00:00
// If the certificate is in the known hosts list, allow the connection
2020-09-27 20:21:56 +00:00
return knownHosts.Lookup(hostname, cert)
2020-09-26 17:59:24 +00:00
}
```
Advanced clients can prompt the user for what to do when encountering an unknown certificate:
```go
2020-09-27 20:21:56 +00:00
client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
2020-09-26 18:34:15 +00:00
err := knownHosts.Lookup(cert)
if err != nil {
switch err {
case gemini.ErrCertificateNotTrusted:
// Alert the user that the certificate is not trusted
2020-09-27 20:21:56 +00:00
fmt.Printf("Warning: certificate for %s is not trusted!\n", hostname)
fmt.Println("This could indicate a Man-in-the-Middle attack.")
2020-09-26 18:34:15 +00:00
case gemini.ErrCertificateUnknown:
// Prompt the user to trust the certificate
if userTrustsCertificateTemporarily() {
// Temporarily trust the certificate
return nil
2020-09-27 17:50:48 +00:00
} else if userTrustsCertificatePermanently() {
2020-09-26 18:34:15 +00:00
// Add the certificate to the known hosts file
knownHosts.Add(cert)
return nil
}
}
2020-09-26 18:34:15 +00:00
}
return err
}
```
2020-09-27 20:21:56 +00:00
See `examples/client` for an example client.