Provide Handler with client certificate information

This commit is contained in:
adnano 2020-09-21 19:17:10 -04:00
parent 81974a9e00
commit 39552c0f8f
4 changed files with 55 additions and 38 deletions

View File

@ -15,21 +15,7 @@ var (
) )
// Client is a Gemini client. // Client is a Gemini client.
// To use a client-side certificate, provide it here. type Client struct{}
//
// 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
}
// Request makes a request for the provided URL. The host is inferred from the URL. // Request makes a request for the provided URL. The host is inferred from the URL.
func (c *Client) Request(url string) (*Response, error) { 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. // 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 { type Request struct {
Host string // host or host:port Host string // host or host:port
URL *url.URL // The URL to request URL *url.URL // the requested URL
Certificates []tls.Certificate // client certificates
} }
// NewRequest returns a new request. The host is inferred from the provided url. // 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" host += ":1965"
} }
config := tls.Config{}
// Allow self signed certificates // Allow self signed certificates
config := c.TLSConfig
config.InsecureSkipVerify = true config.InsecureSkipVerify = true
config.Certificates = req.Certificates
conn, err := tls.Dial("tcp", host, &config) conn, err := tls.Dial("tcp", host, &config)
if err != nil { if err != nil {

View File

@ -13,6 +13,7 @@ import (
) )
var client gemini.Client var client gemini.Client
var cert tls.Certificate
func init() { func init() {
// Configure a client side certificate. // Configure a client side certificate.
@ -22,17 +23,20 @@ func init() {
// openssl ecparam -genkey -name secp384r1 -out client.key // openssl ecparam -genkey -name secp384r1 -out client.key
// openssl req -new -x509 -sha256 -key client.key -out client.crt -days 3650 // openssl req -new -x509 -sha256 -key client.key -out client.crt -days 3650
// //
config := tls.Config{} var err error
cert, err := tls.LoadX509KeyPair("examples/client/client.crt", "examples/client/client.key") cert, err = tls.LoadX509KeyPair("examples/client/client.crt", "examples/client/client.key")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
config.Certificates = append(config.Certificates, cert)
client.TLSConfig = config
} }
func makeRequest(url string) { 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -4,9 +4,9 @@ package main
import ( import (
"crypto/tls" "crypto/tls"
"git.sr.ht/~adnano/go-gemini"
"log" "log"
"net/url"
"git.sr.ht/~adnano/go-gemini"
) )
func main() { func main() {
@ -23,13 +23,15 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
config.Certificates = append(config.Certificates, cert) config.Certificates = append(config.Certificates, cert)
config.ClientAuth = tls.RequestClientCert
mux := &gemini.Mux{} 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{ return &gemini.Response{
Status: gemini.StatusSuccess, Status: gemini.StatusSuccess,
Meta: "text/gemini", Meta: "text/gemini",
Body: []byte("You requested " + url.String()), Body: []byte("You requested " + req.URL.String()),
} }
}) })

View File

@ -2,6 +2,7 @@ package gemini
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509"
"io" "io"
"net" "net"
"net/url" "net/url"
@ -99,17 +100,29 @@ func (s *Server) Serve(ln net.Listener) error {
if err != nil { if err != nil {
continue 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) resp.Write(rw)
rw.Close() rw.Close()
} }
} }
// RequestInfo contains information about a request.
type RequestInfo struct {
URL *url.URL
Certificates []*x509.Certificate
}
// A Handler responds to a Gemini request. // A Handler responds to a Gemini request.
type Handler interface { type Handler interface {
// Serve accepts a url, as that is the only information that is provided in // Serve accepts a Request and returns a Response.
// a Gemini request. Serve(*RequestInfo) *Response
Serve(*url.URL) *Response
} }
// Mux is a Gemini request multiplexer. // 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. // 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) handler := HandlerFunc(handlerFunc)
m.Handle(pattern, handler) m.Handle(pattern, handler)
} }
// Serve responds to the request with the appropriate handler. // Serve responds to the request with the appropriate handler.
func (m *Mux) Serve(url *url.URL) *Response { func (m *Mux) Serve(req *RequestInfo) *Response {
h := m.match(url) h := m.match(req.URL)
if h == nil { if h == nil {
return &Response{ return &Response{
Status: StatusNotFound, Status: StatusNotFound,
Meta: "Not found", Meta: "Not found",
} }
} }
return h.Serve(url) return h.Serve(req)
} }
// A wrapper around a bare function that implements Handler. // 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 { func (f HandlerFunc) Serve(req *RequestInfo) *Response {
return f(url) return f(req)
} }