From ae4b458964700692dd2bc03ba3f269ea88269fda Mon Sep 17 00:00:00 2001 From: adnano Date: Sun, 27 Sep 2020 23:49:41 -0400 Subject: [PATCH] Generate certificates on demand --- cert.go | 32 +++++++++++++++----------------- client.go | 12 ++++++------ examples/cert/cert.go | 8 +++++--- examples/client/client.go | 24 ++++++++++++++++++++---- 4 files changed, 46 insertions(+), 30 deletions(-) diff --git a/cert.go b/cert.go index 8c82aa4..90bb514 100644 --- a/cert.go +++ b/cert.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/ed25519" "crypto/rand" + "crypto/tls" "crypto/x509" "encoding/pem" "math/big" @@ -14,27 +15,25 @@ import ( ) // CertificateStore maps hostnames to certificates. -type CertificateStore struct { - store map[string]*x509.Certificate // map of hostnames to certificates +type CertificateStore map[string]*tls.Certificate + +// NewCertificateStore creates and returns a new certificate store. +func NewCertificateStore() CertificateStore { + return map[string]*tls.Certificate{} } -func NewCertificateStore() *CertificateStore { - return &CertificateStore{ - store: map[string]*x509.Certificate{}, +// NewCertificate creates and returns a new parsed certificate. +func NewCertificate(host string, duration time.Duration) (tls.Certificate, error) { + crt, key, err := NewRawCertificate(host, duration) + if err != nil { + return tls.Certificate{}, err } + return tls.X509KeyPair(crt, key) } -func (c *CertificateStore) Put(hostname string, cert *x509.Certificate) { - c.store[hostname] = cert -} - -func (c *CertificateStore) Get(hostname string) *x509.Certificate { - return c.store[hostname] -} - -// NewCertificate creates and returns a raw certificate for the given host. +// NewRawCertificate creates and returns a raw certificate for the given host. // It generates a self-signed TLS certificate and a ED25519 private key. -func NewCertificate(host string) (crt, key []byte, err error) { +func NewRawCertificate(host string, duration time.Duration) (crt, key []byte, err error) { // Generate a ED25519 private key _, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { @@ -47,8 +46,7 @@ func NewCertificate(host string) (crt, key []byte, err error) { keyUsage := x509.KeyUsageDigitalSignature notBefore := time.Now() - validFor := 365 * 24 * time.Hour - notAfter := notBefore.Add(validFor) + notAfter := notBefore.Add(duration) // Generate the serial number serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) diff --git a/client.go b/client.go index 504fd18..37b1b6d 100644 --- a/client.go +++ b/client.go @@ -185,11 +185,11 @@ type Client struct { KnownHosts KnownHosts // CertificateStore contains all the certificates that the client has stored. - CertificateStore *CertificateStore + CertificateStore CertificateStore // GetCertificate, if not nil, will be called to determine which certificate // (if any) should be used for a request. - GetCertificate func(req *Request, store *CertificateStore) *tls.Certificate + GetCertificate func(hostname string, store CertificateStore) *tls.Certificate // TrustCertificate, if not nil, will be called to determine whether the // client should trust the given certificate. @@ -205,14 +205,14 @@ func (c *Client) Send(req *Request) (*Response, error) { MinVersion: tls.VersionTLS12, GetClientCertificate: func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) { if c.GetCertificate != nil { - if cert := c.GetCertificate(req, c.CertificateStore); cert != nil { + if cert := c.GetCertificate(req.Hostname(), c.CertificateStore); cert != nil { return cert, nil } } - if req.Certificate == nil { - return &tls.Certificate{}, nil + if req.Certificate != nil { + return req.Certificate, nil } - return req.Certificate, nil + return &tls.Certificate{}, nil }, VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error { // Parse the certificate diff --git a/examples/cert/cert.go b/examples/cert/cert.go index 23975c0..b9a284f 100644 --- a/examples/cert/cert.go +++ b/examples/cert/cert.go @@ -4,19 +4,21 @@ package main import ( "log" + "time" - "git.sr.ht/~adnano/go-gemini" + gmi "git.sr.ht/~adnano/go-gemini" ) func main() { host := "localhost" - crt, key, err := gemini.NewCertificate(host) + duration := 365 * 24 * time.Hour + crt, key, err := gmi.NewRawCertificate(host, duration) if err != nil { log.Fatal(err) } - if err := gemini.WriteCertificate(host, crt, key); err != nil { + if err := gmi.WriteCertificate(host, crt, key); err != nil { log.Fatal(err) } } diff --git a/examples/client/client.go b/examples/client/client.go index 22c96de..a64365f 100644 --- a/examples/client/client.go +++ b/examples/client/client.go @@ -4,21 +4,22 @@ package main import ( "bufio" + "crypto/tls" "crypto/x509" "fmt" "os" + "time" - "git.sr.ht/~adnano/go-gemini" + gmi "git.sr.ht/~adnano/go-gemini" ) var ( scanner = bufio.NewScanner(os.Stdin) - client *gmi.Client + client = &gmi.Client{} ) func init() { // Initialize the client - client = &gmi.Client{} client.KnownHosts.Load() // Load known hosts client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gmi.KnownHosts) error { err := knownHosts.Lookup(hostname, cert) @@ -45,6 +46,21 @@ func init() { } return err } + + client.CertificateStore = gmi.NewCertificateStore() + client.GetCertificate = func(hostname string, store gmi.CertificateStore) *tls.Certificate { + if cert, ok := store[hostname]; ok { + return cert + } + // Generate a certificate + duration := time.Hour + cert, err := gmi.NewCertificate(hostname, duration) + if err != nil { + return nil + } + store[hostname] = &cert + return &cert + } } // sendRequest sends a request to the given URL. @@ -67,7 +83,7 @@ func sendRequest(req *gmi.Request) error { case gmi.StatusClassRedirect: fmt.Println("Redirecting to", resp.Meta) // Make the request to the same host - red, err := gmi.NewRequestTo(req.Host, resp.Meta) + red, err := gmi.NewRequestTo(resp.Meta, req.Host) if err != nil { return err }