diff --git a/cert.go b/cert.go index 6ecf119..7c151f6 100644 --- a/cert.go +++ b/cert.go @@ -8,7 +8,6 @@ import ( "crypto/x509" "math/big" "net" - "path" "path/filepath" "strings" "time" @@ -36,9 +35,9 @@ func (c *CertificateStore) Add(scope string, cert tls.Certificate) { c.store[scope] = cert } -// Lookup returns the certificate for the given hostname. -func (c *CertificateStore) Lookup(hostname string) (*tls.Certificate, error) { - cert, ok := c.store[hostname] +// Lookup returns the certificate for the given scope. +func (c *CertificateStore) Lookup(scope string) (*tls.Certificate, error) { + cert, ok := c.store[scope] if !ok { return nil, ErrCertificateUnknown } @@ -49,25 +48,9 @@ func (c *CertificateStore) Lookup(hostname string) (*tls.Certificate, error) { return &cert, nil } -// lookup returns the certificate for the given hostname + path. -func (c *CertificateStore) lookup(scope string) (*tls.Certificate, error) { - for { - cert, err := c.Lookup(scope) - switch err { - case ErrCertificateExpired, nil: - return cert, err - } - scope = path.Dir(scope) - if scope == "." { - break - } - } - return nil, ErrCertificateUnknown -} - // Load loads certificates from the given path. // The path should lead to a directory containing certificates and private keys -// in the form hostname.crt and hostname.key. +// in the form scope.crt and scope.key. // For example, the hostname "localhost" would have the corresponding files // localhost.crt (certificate) and localhost.key (private key). func (c *CertificateStore) Load(path string) error { @@ -81,8 +64,8 @@ func (c *CertificateStore) Load(path string) error { if err != nil { continue } - hostname := strings.TrimSuffix(filepath.Base(crtPath), ".crt") - c.Add(hostname, cert) + scope := strings.TrimSuffix(filepath.Base(crtPath), ".crt") + c.Add(scope, cert) } return nil } diff --git a/client.go b/client.go index 5ab3ded..5d58895 100644 --- a/client.go +++ b/client.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "net" "net/url" + "path" "strings" ) @@ -153,12 +154,23 @@ func (c *Client) getClientCertificate(req *Request) (*tls.Certificate, error) { if req.Certificate != nil { return req.Certificate, nil } - hostname, path := req.URL.Hostname(), strings.TrimSuffix(req.URL.Path, "/") - if cert, err := c.Certificates.lookup(hostname + path); err == nil { - // Remember the certificate used - req.Certificate = cert - return cert, nil + + // Search recursively for the certificate + scope := req.URL.Hostname() + strings.TrimSuffix(req.URL.Path, "/") + for { + cert, err := c.Certificates.Lookup(scope) + if err == nil { + return cert, err + } + if err == ErrCertificateExpired { + break + } + scope = path.Dir(scope) + if scope == "." { + break + } } + return &tls.Certificate{}, nil } diff --git a/doc.go b/doc.go index 3c97595..f0d3fdb 100644 --- a/doc.go +++ b/doc.go @@ -41,22 +41,12 @@ Clients can control when to trust certificates with TrustCertificate: return knownHosts.Lookup(hostname, cert) } -Clients can control what to do when a server requests a certificate: +Clients can create client certificates upon the request of a server: - client.GetCertificate = func(hostname string, store *gemini.CertificateStore) *tls.Certificate { - // If the certificate is in the store, return it - if cert, err := store.Lookup(hostname); err == nil { - return &cert - } - // Otherwise, generate a certificate - duration := time.Hour - cert, err := gemini.NewCertificate(hostname, duration) - if err != nil { - return nil - } - // Store and return the certificate - store.Add(hostname, cert) - return &cert + client.CreateCertificate = func(hostname, path string) *tls.Certificate { + return gemini.CreateCertificate(gemini.CertificateOptions{ + Duration: time.Hour, + }) } Server is a Gemini server. @@ -65,7 +55,7 @@ Server is a Gemini server. Servers must be configured with certificates: - err := server.CertificateStore.Load("/var/lib/gemini/certs") + err := server.Certificates.Load("/var/lib/gemini/certs") if err != nil { // handle error } diff --git a/server.go b/server.go index f991f08..5d2f724 100644 --- a/server.go +++ b/server.go @@ -36,9 +36,16 @@ type responderKey struct { } // Register registers a responder for the given pattern. -// Patterns must be in the form of scheme://hostname (e.g. gemini://example.com). +// +// Patterns must be in the form of hostname or scheme://hostname +// (e.g. gemini://example.com). // If no scheme is specified, a default scheme of gemini:// is assumed. +// // Wildcard patterns are supported (e.g. *.example.com). +// To register a certificate for a wildcard domain, call Certificates.Add: +// +// var s gemini.Server +// s.Certificates.Add("*.example.com", cert) func (s *Server) Register(pattern string, responder Responder) { if pattern == "" { panic("gemini: invalid pattern")