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.
// 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 {

View File

@ -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)
}

View File

@ -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()),
}
})

View File

@ -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)
}