diff --git a/client.go b/client.go index 6a55a4d..913a655 100644 --- a/client.go +++ b/client.go @@ -15,21 +15,7 @@ var ( ) // Client is a Gemini client. -// To use a client-side certificate, provide it here. -// -// Example: -// -// config := tls.Config{} -// cert, err := tls.LoadX509KeyPair("client.crt", "client.key") -// if err != nil { -// panic(err) -// } -// config.Certificates = append(config.Certificates, cert) -// -type Client struct { - // The client's TLS configuration. - TLSConfig tls.Config -} +type Client struct{} // Request makes a request for the provided URL. The host is inferred from the URL. func (c *Client) Request(url string) (*Response, error) { @@ -50,9 +36,20 @@ func (c *Client) ProxyRequest(host, url string) (*Response, error) { } // Request is a Gemini request. +// +// A Request can optionally be configured with a client certificate. Example: +// +// req := NewRequest(url) +// cert, err := tls.LoadX509KeyPair("client.crt", "client.key") +// if err != nil { +// panic(err) +// } +// req.Certificates = append(req.Certificates, cert) +// type Request struct { - Host string // host or host:port - URL *url.URL // The URL to request + Host string // host or host:port + URL *url.URL // the requested URL + Certificates []tls.Certificate // client certificates } // NewRequest returns a new request. The host is inferred from the provided url. @@ -95,9 +92,10 @@ func (c *Client) Do(req *Request) (*Response, error) { host += ":1965" } + config := tls.Config{} // Allow self signed certificates - config := c.TLSConfig config.InsecureSkipVerify = true + config.Certificates = req.Certificates conn, err := tls.Dial("tcp", host, &config) if err != nil { diff --git a/examples/client/client.go b/examples/client/client.go index 6289e95..3864789 100644 --- a/examples/client/client.go +++ b/examples/client/client.go @@ -13,6 +13,7 @@ import ( ) var client gemini.Client +var cert tls.Certificate func init() { // Configure a client side certificate. @@ -22,17 +23,20 @@ func init() { // openssl ecparam -genkey -name secp384r1 -out client.key // openssl req -new -x509 -sha256 -key client.key -out client.crt -days 3650 // - config := tls.Config{} - cert, err := tls.LoadX509KeyPair("examples/client/client.crt", "examples/client/client.key") + var err error + cert, err = tls.LoadX509KeyPair("examples/client/client.crt", "examples/client/client.key") if err != nil { log.Fatal(err) } - config.Certificates = append(config.Certificates, cert) - client.TLSConfig = config } func makeRequest(url string) { - resp, err := client.Request(url) + req, err := gemini.NewRequest(url) + if err != nil { + log.Fatal(err) + } + req.Certificates = append(req.Certificates, cert) + resp, err := client.Do(req) if err != nil { log.Fatal(err) } diff --git a/examples/server/server.go b/examples/server/server.go index 9165898..1040af9 100644 --- a/examples/server/server.go +++ b/examples/server/server.go @@ -4,9 +4,9 @@ package main import ( "crypto/tls" - "git.sr.ht/~adnano/go-gemini" "log" - "net/url" + + "git.sr.ht/~adnano/go-gemini" ) func main() { @@ -23,13 +23,15 @@ func main() { log.Fatal(err) } config.Certificates = append(config.Certificates, cert) + config.ClientAuth = tls.RequestClientCert mux := &gemini.Mux{} - mux.HandleFunc("/", func(url *url.URL) *gemini.Response { + mux.HandleFunc("/", func(req *gemini.RequestInfo) *gemini.Response { + log.Printf("Request for %s with certificates %v", req.URL.String(), req.Certificates) return &gemini.Response{ Status: gemini.StatusSuccess, Meta: "text/gemini", - Body: []byte("You requested " + url.String()), + Body: []byte("You requested " + req.URL.String()), } }) diff --git a/server.go b/server.go index a42588b..134dfc2 100644 --- a/server.go +++ b/server.go @@ -2,6 +2,7 @@ package gemini import ( "crypto/tls" + "crypto/x509" "io" "net" "net/url" @@ -99,17 +100,29 @@ func (s *Server) Serve(ln net.Listener) error { if err != nil { continue } - resp := s.Handler.Serve(url) + + // Gather information about the request + certs := rw.(*tls.Conn).ConnectionState().PeerCertificates + reqInfo := &RequestInfo{ + URL: url, + Certificates: certs, + } + resp := s.Handler.Serve(reqInfo) resp.Write(rw) rw.Close() } } +// RequestInfo contains information about a request. +type RequestInfo struct { + URL *url.URL + Certificates []*x509.Certificate +} + // A Handler responds to a Gemini request. type Handler interface { - // Serve accepts a url, as that is the only information that is provided in - // a Gemini request. - Serve(*url.URL) *Response + // Serve accepts a Request and returns a Response. + Serve(*RequestInfo) *Response } // Mux is a Gemini request multiplexer. @@ -153,26 +166,26 @@ func (m *Mux) Handle(pattern string, handler Handler) { } // HandleFunc registers a HandlerFunc for the given pattern. -func (m *Mux) HandleFunc(pattern string, handlerFunc func(url *url.URL) *Response) { +func (m *Mux) HandleFunc(pattern string, handlerFunc func(req *RequestInfo) *Response) { handler := HandlerFunc(handlerFunc) m.Handle(pattern, handler) } // Serve responds to the request with the appropriate handler. -func (m *Mux) Serve(url *url.URL) *Response { - h := m.match(url) +func (m *Mux) Serve(req *RequestInfo) *Response { + h := m.match(req.URL) if h == nil { return &Response{ Status: StatusNotFound, Meta: "Not found", } } - return h.Serve(url) + return h.Serve(req) } // A wrapper around a bare function that implements Handler. -type HandlerFunc func(url *url.URL) *Response +type HandlerFunc func(req *RequestInfo) *Response -func (f HandlerFunc) Serve(url *url.URL) *Response { - return f(url) +func (f HandlerFunc) Serve(req *RequestInfo) *Response { + return f(req) }