From 2eb7fb9ba48751cb26efd6324285be3f1f4a98da Mon Sep 17 00:00:00 2001 From: adnano Date: Sun, 27 Sep 2020 13:50:48 -0400 Subject: [PATCH] Implement certificate creation --- .gitignore | 2 + README.md | 2 +- cert.go | 130 +++++++++++++++++++++++++++++++++++++ client.go | 4 +- examples/cert/cert.go | 22 +++++++ examples/client/.gitignore | 2 - examples/client/client.go | 2 +- examples/server/.gitignore | 2 - examples/server/server.go | 2 +- store.go | 24 ------- 10 files changed, 159 insertions(+), 33 deletions(-) create mode 100644 .gitignore create mode 100644 cert.go create mode 100644 examples/cert/cert.go delete mode 100644 examples/client/.gitignore delete mode 100644 examples/server/.gitignore delete mode 100644 store.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be870b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.crt +*.key diff --git a/README.md b/README.md index 70b4b53..e0c3501 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ client.TrustCertificate = func(cert *x509.Certificate, knownHosts *gemini.KnownH if userTrustsCertificateTemporarily() { // Temporarily trust the certificate return nil - } else if user.TrustsCertificatePermanently() { + } else if userTrustsCertificatePermanently() { // Add the certificate to the known hosts file knownHosts.Add(cert) return nil diff --git a/cert.go b/cert.go new file mode 100644 index 0000000..e9c4863 --- /dev/null +++ b/cert.go @@ -0,0 +1,130 @@ +package gemini + +import ( + "bytes" + "crypto/ed25519" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "math/big" + "net" + "os" + "strings" + "time" +) + +// CertificateStore maps hostnames to certificates. +type CertificateStore struct { + store map[string]*x509.Certificate // map of hostnames to certificates +} + +func NewCertificateStore() *CertificateStore { + return &CertificateStore{ + store: map[string]*x509.Certificate{}, + } +} + +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. +// It generates a self-signed TLS certificate and a ED25519 private key. +func NewCertificate(host string) (crt, key []byte, err error) { + // Generate a ED25519 private key + _, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, nil, err + } + public := priv.Public().(ed25519.PublicKey) + + // ED25519 keys should have the DigitalSignature KeyUsage bits set + // in the x509.Certificate template + keyUsage := x509.KeyUsageDigitalSignature + + notBefore := time.Now() + validFor := 365 * 24 * time.Hour + notAfter := notBefore.Add(validFor) + + // Generate the serial number + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, nil, err + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: keyUsage, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + hosts := strings.Split(host, ",") + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + // Create the certificate + cert, err := x509.CreateCertificate(rand.Reader, &template, &template, public, priv) + if err != nil { + return nil, nil, err + } + + // Encode the certificate + var b bytes.Buffer + if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil { + return nil, nil, err + } + crt = b.Bytes() + + // Encode the key + b = bytes.Buffer{} + if err != nil { + return nil, nil, err + } + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return nil, nil, err + } + if err := pem.Encode(&b, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + return nil, nil, err + } + key = b.Bytes() + + return +} + +// WriteCertificate writes the provided certificate and private key to name.crt + name.key +func WriteCertificate(name string, crt, key []byte) error { + // Write the certificate + crtPath := name + ".crt" + crtOut, err := os.OpenFile(crtPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + if _, err := crtOut.Write(crt); err != nil { + return err + } + + // Write the private key + keyPath := name + ".key" + keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + if _, err := keyOut.Write(key); err != nil { + return err + } + return nil +} diff --git a/client.go b/client.go index 7af5792..57c144a 100644 --- a/client.go +++ b/client.go @@ -205,8 +205,8 @@ func (c *Client) Send(req *Request) (*Response, error) { return err } // Check that the certificate is valid for the hostname - if cert.Subject.CommonName != hostname(req.Host) { - return ErrCertificateNotValid + if err := cert.VerifyHostname(req.Host); err != nil { + return err } // Check that the client trusts the certificate if c.TrustCertificate == nil { diff --git a/examples/cert/cert.go b/examples/cert/cert.go new file mode 100644 index 0000000..23975c0 --- /dev/null +++ b/examples/cert/cert.go @@ -0,0 +1,22 @@ +// +build example + +package main + +import ( + "log" + + "git.sr.ht/~adnano/go-gemini" +) + +func main() { + host := "localhost" + + crt, key, err := gemini.NewCertificate(host) + if err != nil { + log.Fatal(err) + } + + if err := gemini.WriteCertificate(host, crt, key); err != nil { + log.Fatal(err) + } +} diff --git a/examples/client/.gitignore b/examples/client/.gitignore deleted file mode 100644 index 37278c1..0000000 --- a/examples/client/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -client.crt -client.key diff --git a/examples/client/client.go b/examples/client/client.go index 8bbc6b8..69e68e6 100644 --- a/examples/client/client.go +++ b/examples/client/client.go @@ -59,7 +59,7 @@ func init() { // openssl ecparam -genkey -name secp384r1 -out client.key // openssl req -new -x509 -sha512 -key client.key -out client.crt -days 365 // - cert, err = tls.LoadX509KeyPair("examples/client/client.crt", "examples/client/client.key") + cert, err = tls.LoadX509KeyPair("examples/client/localhost.crt", "examples/client/localhost.key") if err != nil { log.Fatal(err) } diff --git a/examples/server/.gitignore b/examples/server/.gitignore deleted file mode 100644 index 10cdeb2..0000000 --- a/examples/server/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -server.crt -server.key diff --git a/examples/server/server.go b/examples/server/server.go index 03dfc2b..62608c7 100644 --- a/examples/server/server.go +++ b/examples/server/server.go @@ -17,7 +17,7 @@ func main() { // openssl ecparam -genkey -name secp384r1 -out server.key // openssl req -new -x509 -sha512 -key server.key -out server.crt -days 365 // - cert, err := tls.LoadX509KeyPair("examples/server/server.crt", "examples/server/server.key") + cert, err := tls.LoadX509KeyPair("examples/server/localhost.crt", "examples/server/localhost.key") if err != nil { log.Fatal(err) } diff --git a/store.go b/store.go deleted file mode 100644 index 27ab955..0000000 --- a/store.go +++ /dev/null @@ -1,24 +0,0 @@ -package gemini - -import ( - "crypto/x509" -) - -// CertificateStore maps hostnames to certificates. -type CertificateStore struct { - store map[string]*x509.Certificate // map of hostnames to certificates -} - -func NewCertificateStore() *CertificateStore { - return &CertificateStore{ - store: map[string]*x509.Certificate{}, - } -} - -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] -}