aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoradnano <[email protected]>2020-09-25 18:53:20 -0400
committeradnano <[email protected]>2020-09-25 18:53:20 -0400
commitace3e682deefbfc15b9f5ee01c73250dcb4f7a5a (patch)
tree02423af4ed12b1b06053b5632d2562283c02e34e
parentAdd test (diff)
downloadgo-gemini-ace3e682deefbfc15b9f5ee01c73250dcb4f7a5a.tar.xz
go-gemini-ace3e682deefbfc15b9f5ee01c73250dcb4f7a5a.zip
Remove TLSConfig fields
-rw-r--r--README.md36
-rw-r--r--examples/client/client.go7
-rw-r--r--examples/server/server.go16
-rw-r--r--gemini.go51
-rw-r--r--tofu.go70
5 files changed, 101 insertions, 79 deletions
diff --git a/README.md b/README.md
index 666f6b0..cd49501 100644
--- a/README.md
+++ b/README.md
@@ -32,40 +32,12 @@ A quick overview of the Gemini protocol:
The way this is implemented in this package is like so:
-1. Client makes a request with `NewRequest`. The client can verify server
- certificates in the Request options, see [Recommended TLS
- configuration](#recommended-tls-configuration).
+1. Client makes a request with `NewRequest`. The client then sends the request
+ with `Do(*Request) (*Response, error)`.
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.
5. Client recieves the response as a `*Response`. The client then handles the
- response. The client can now verify the certificate of the server using a
- Trust-On-First-Use method.
-
-## Recommended TLS configuration
-
-For clients, the recommended TLS configuration is as follows:
-
-```go
-// Accept self-signed server certificates
-req.TLSConfig.InsecureSkipVerify = true
-// Manually verify server certificates, using TOFU
-req.TLSConfig.VerifyPeerCertificate = func(rawCerts [][]byte, chains [][]*x509.Certificate) error {
- // Verify the server certificate here
- // Return an error on failure, or nil on success
- return nil
-}
-```
-
-Note that `gemini.Get` does not verify server certificates.
-
-For servers, the recommended TLS configuration is as follows:
-
-```go
-// Specify a certificate
-// To load a certificate, use `tls.LoadX509KeyPair`.
-srv.TLSConfig.Certificates = append(srv.TLSConfig.Certificates, cert)
-// Request client certificates
-srv.TLSConfig.ClientAuth = tls.RequestClientCert
-```
+ response. The client can now verify the certificate of the server using a
+ Trust-On-First-Use method.
diff --git a/examples/client/client.go b/examples/client/client.go
index 1716955..a90af18 100644
--- a/examples/client/client.go
+++ b/examples/client/client.go
@@ -34,8 +34,7 @@ func makeRequest(url string) {
if err != nil {
log.Fatal(err)
}
- req.TLSConfig.InsecureSkipVerify = true
- req.TLSConfig.Certificates = append(req.TLSConfig.Certificates, cert)
+ req.Certificate = cert
resp, err := gemini.Do(req)
if err != nil {
log.Fatal(err)
@@ -63,9 +62,9 @@ func makeRequest(url string) {
case gemini.StatusClassPermanentFailure:
log.Fatal("Permanent failure")
case gemini.StatusClassClientCertificateRequired:
- log.Fatal("Client Certificate Required")
+ log.Fatal("Client certificate required")
default:
- log.Fatal("Protocol Error")
+ log.Fatal("Protocol error")
}
}
diff --git a/examples/server/server.go b/examples/server/server.go
index c587c69..02e8643 100644
--- a/examples/server/server.go
+++ b/examples/server/server.go
@@ -4,7 +4,6 @@ package main
import (
"crypto/tls"
- "crypto/x509"
"log"
"git.sr.ht/~adnano/go-gemini"
@@ -18,27 +17,24 @@ func main() {
// openssl ecparam -genkey -name secp384r1 -out server.key
// openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
//
- config := tls.Config{}
cert, err := tls.LoadX509KeyPair("examples/server/server.crt", "examples/server/server.key")
if err != nil {
log.Fatal(err)
}
- config.Certificates = append(config.Certificates, cert)
- config.ClientAuth = tls.RequestClientCert
- config.VerifyPeerCertificate = func(rawCerts [][]byte, chains [][]*x509.Certificate) error {
- return nil
- }
mux := &gemini.ServeMux{}
mux.HandleFunc("/", func(rw *gemini.ResponseWriter, req *gemini.Request) {
- log.Printf("Request from %s for %s with certificates %v", req.RemoteAddr.String(), req.URL.String(), req.TLS.PeerCertificates)
rw.WriteHeader(gemini.StatusSuccess, "text/gemini")
rw.Write([]byte("You requested " + req.URL.String()))
+ log.Printf("Request from %s for %s", req.RemoteAddr.String(), req.URL)
+ if len(req.TLS.PeerCertificates) != 0 {
+ log.Print("Client certificate: ", gemini.Fingerprint(req.TLS.PeerCertificates[0]))
+ }
})
server := gemini.Server{
- TLSConfig: config,
- Handler: mux,
+ Handler: mux,
+ Certificate: cert,
}
server.ListenAndServe()
}
diff --git a/gemini.go b/gemini.go
index 76b0098..1adf284 100644
--- a/gemini.go
+++ b/gemini.go
@@ -78,12 +78,8 @@ type Request struct {
// This field is ignored by the server.
Host string
- // TLSConfig provides a TLS configuration for use by the client.
- // It is recommended that clients set `InsecureSkipVerify` to true to skip
- // verifying TLS certificates, and instead adopt a Trust-On-First-Use
- // method of verifying certificates.
- // This field is ignored by the server.
- TLSConfig tls.Config
+ // The certificate to use for the request.
+ Certificate tls.Certificate
// RemoteAddr allows servers and other software to record the network
// address that sent the request.
@@ -133,7 +129,7 @@ func NewProxyRequest(host, rawurl string) (*Request, error) {
// write writes the Gemini request to the provided buffered writer.
func (r *Request) write(w *bufio.Writer) error {
url := r.URL.String()
- // UserInfo is invalid
+ // User is invalid
if r.URL.User != nil || len(url) > 1024 {
return ErrInvalidURL
}
@@ -165,32 +161,14 @@ type Response struct {
TLS tls.ConnectionState
}
-// Get makes a request for the provided URL. The host is inferred from the URL.
-//
-// Get does not verify server certificates. To verify certificates, use a Request.
-func Get(url string) (*Response, error) {
- req, err := NewRequest(url)
- if err != nil {
- return nil, err
- }
- req.TLSConfig.InsecureSkipVerify = true
- return Do(req)
-}
-
-// ProxyGet requests the provided URL from the provided host.
-func ProxyGet(host, url string) (*Response, error) {
- req, err := NewProxyRequest(host, url)
- if err != nil {
- return nil, err
- }
- req.TLSConfig.InsecureSkipVerify = true
- return Do(req)
-}
-
// Do sends a Gemini request and returns a Gemini response.
func Do(req *Request) (*Response, error) {
// Connect to the host
- conn, err := tls.Dial("tcp", req.Host, &req.TLSConfig)
+ config := &tls.Config{
+ InsecureSkipVerify: true,
+ Certificates: []tls.Certificate{req.Certificate},
+ }
+ conn, err := tls.Dial("tcp", req.Host, config)
if err != nil {
return nil, err
}
@@ -267,8 +245,9 @@ type Server struct {
// If Addr is empty, the server will listen on the address ":1965".
Addr string
- // TLSConfig provides a TLS configuration for use by the server.
- TLSConfig tls.Config
+ // Certificate provides a TLS certificate for use by the server.
+ // Using a self-signed certificate is recommended.
+ Certificate tls.Certificate
// Handler specifies the Handler for requests.
// If Handler is not set, the server will error.
@@ -278,6 +257,7 @@ type Server struct {
// ListenAndServe listens for requests at the server's configured address.
func (s *Server) ListenAndServe() error {
addr := s.Addr
+
if addr == "" {
addr = ":1965"
}
@@ -288,7 +268,12 @@ func (s *Server) ListenAndServe() error {
}
defer ln.Close()
- tlsListener := tls.NewListener(ln, &s.TLSConfig)
+ config := &tls.Config{
+ InsecureSkipVerify: true,
+ Certificates: []tls.Certificate{s.Certificate},
+ ClientAuth: tls.RequestClientCert,
+ }
+ tlsListener := tls.NewListener(ln, config)
return s.Serve(tlsListener)
}
diff --git a/tofu.go b/tofu.go
new file mode 100644
index 0000000..e4ff39f
--- /dev/null
+++ b/tofu.go
@@ -0,0 +1,70 @@
+package gemini
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/sha512"
+ "crypto/x509"
+ "errors"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+)
+
+var (
+ ErrInvalidKnownHosts = errors.New("gemini: invalid known hosts")
+)
+
+// KnownHost represents a known host.
+type KnownHost struct {
+ Hostname string // e.g. gemini.circumlunar.space
+ Algorithm string // fingerprint algorithm
+ Fingerprint string // fingerprint in hexadecimal, with ':' between each octet
+ NotAfter int64 // unix time of certificate notAfter date
+}
+
+// ParseKnownHosts parses and returns a list of known hosts from the provided io.Reader.
+func ParseKnownHosts(r io.Reader) ([]KnownHost, error) {
+ hosts := []KnownHost{}
+
+ scanner := bufio.NewScanner(r)
+ for scanner.Scan() {
+ text := scanner.Text()
+
+ parts := strings.Split(text, " ")
+ if len(parts) < 4 {
+ return nil, ErrInvalidKnownHosts
+ }
+
+ hostname := parts[0]
+ algorithm := parts[1]
+ fingerprint := parts[2]
+ notAfter, err := strconv.ParseInt(parts[3], 10, 0)
+ if err != nil {
+ return nil, ErrInvalidKnownHosts
+ }
+
+ hosts = append(hosts, KnownHost{
+ Hostname: hostname,
+ Algorithm: algorithm,
+ Fingerprint: fingerprint,
+ NotAfter: notAfter,
+ })
+ }
+
+ return hosts, nil
+}
+
+// Fingerprint returns the SHA-512 fingerprint of the provided certificate.
+func Fingerprint(cert *x509.Certificate) string {
+ sum512 := sha512.Sum512(cert.Raw)
+ var buf bytes.Buffer
+ for i, f := range sum512 {
+ if i > 0 {
+ fmt.Fprintf(&buf, ":")
+ }
+ fmt.Fprintf(&buf, "%02X", f)
+ }
+ return buf.String()
+}