2020-09-21 21:48:42 +02:00
|
|
|
# go-gemini
|
|
|
|
|
2020-09-22 00:28:34 +02: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 04:09:50 +02:00
|
|
|
`go-gemini` implements the [Gemini protocol](https://gemini.circumlunar.space)
|
|
|
|
in Go.
|
2020-09-21 21:48:42 +02:00
|
|
|
|
2020-09-21 23:23:51 +02: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 21:48:42 +02:00
|
|
|
|
2020-09-21 23:23:51 +02:00
|
|
|
## Examples
|
2020-09-21 21:48:42 +02:00
|
|
|
|
2020-09-21 23:23:51 +02:00
|
|
|
See `examples/client` and `examples/server` for an example client and server.
|
2020-09-21 21:48:42 +02:00
|
|
|
|
2020-09-21 23:23:51 +02:00
|
|
|
To run the examples:
|
2020-09-21 21:48:42 +02:00
|
|
|
|
2020-09-22 00:25:31 +02:00
|
|
|
go run -tags=example ./examples/server
|
2020-09-25 02:13:59 +02: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-26 00:53:20 +02:00
|
|
|
1. Client makes a request with `NewRequest`. The client then sends the request
|
2020-09-26 05:06:54 +02:00
|
|
|
with `(*Client).Send(*Request) (*Response, error)`. The client then determines whether
|
2020-09-27 22:21:56 +02:00
|
|
|
to trust the certificate (see [TOFU](#tofu)).
|
2020-09-25 02:13:59 +02: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-26 01:53:50 +02:00
|
|
|
3. Client recieves the response as a `*Response`. The client then handles the
|
|
|
|
response.
|
2020-09-26 05:06:54 +02:00
|
|
|
|
|
|
|
## TOFU
|
|
|
|
|
2020-09-26 19:59:24 +02:00
|
|
|
`go-gemini` makes it easy to implement Trust On First Use in your clients.
|
|
|
|
|
2020-09-27 22:21:56 +02: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
|
|
|
|
// Send uses the default client, which will load the default list of known
|
|
|
|
hosts.
|
|
|
|
req := gemini.NewRequest("gemini://example.com")
|
|
|
|
gemini.Send(req)
|
|
|
|
```
|
|
|
|
|
|
|
|
Clients can also load their own list of known hosts:
|
2020-09-26 05:06:54 +02:00
|
|
|
|
|
|
|
```go
|
2020-09-26 19:59:24 +02:00
|
|
|
client := &Client{}
|
2020-09-27 22:21:56 +02:00
|
|
|
if err := client.KnownHosts.LoadFrom("path/to/my/known_hosts"); err != nil {
|
2020-09-26 19:59:24 +02:00
|
|
|
log.Fatal(err)
|
2020-09-26 05:06:54 +02:00
|
|
|
}
|
|
|
|
```
|
2020-09-26 19:27:03 +02:00
|
|
|
|
2020-09-26 19:59:24 +02:00
|
|
|
Clients can then specify how to trust certificates in the `TrustCertificate`
|
|
|
|
field:
|
|
|
|
|
|
|
|
```go
|
2020-09-27 22:21:56 +02:00
|
|
|
client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
|
2020-09-26 19:59:24 +02:00
|
|
|
// If the certificate is in the known hosts list, allow the connection
|
2020-09-27 22:21:56 +02:00
|
|
|
return knownHosts.Lookup(hostname, cert)
|
2020-09-26 19:59:24 +02:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Advanced clients can prompt the user for what to do when encountering an unknown certificate:
|
|
|
|
|
2020-09-26 19:27:03 +02:00
|
|
|
```go
|
2020-09-27 22:21:56 +02:00
|
|
|
client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
|
2020-09-26 20:34:15 +02: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 22:21:56 +02: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 20:34:15 +02:00
|
|
|
case gemini.ErrCertificateUnknown:
|
|
|
|
// Prompt the user to trust the certificate
|
|
|
|
if userTrustsCertificateTemporarily() {
|
|
|
|
// Temporarily trust the certificate
|
|
|
|
return nil
|
2020-09-27 19:50:48 +02:00
|
|
|
} else if userTrustsCertificatePermanently() {
|
2020-09-26 20:34:15 +02:00
|
|
|
// Add the certificate to the known hosts file
|
|
|
|
knownHosts.Add(cert)
|
|
|
|
return nil
|
2020-09-26 19:27:03 +02:00
|
|
|
}
|
|
|
|
}
|
2020-09-26 20:34:15 +02:00
|
|
|
}
|
|
|
|
return err
|
2020-09-26 19:27:03 +02:00
|
|
|
}
|
|
|
|
```
|
2020-09-27 22:21:56 +02:00
|
|
|
|
|
|
|
See `examples/client` for an example client.
|