diff --git a/cert.go b/cert.go index 6fcf77d..6443ed8 100644 --- a/cert.go +++ b/cert.go @@ -1,12 +1,11 @@ package gmi import ( - "bytes" + "crypto" "crypto/ed25519" "crypto/rand" "crypto/tls" "crypto/x509" - "encoding/pem" "math/big" "net" "path/filepath" @@ -73,37 +72,39 @@ func (c *CertificateStore) Load(path string) error { // NewCertificate creates and returns a new parsed certificate. func NewCertificate(host string, duration time.Duration) (tls.Certificate, error) { - crt, key, err := NewRawCertificate(host, duration) + crt, priv, err := newX509KeyPair(host, duration) if err != nil { return tls.Certificate{}, err } - return tls.X509KeyPair(crt, key) + var cert tls.Certificate + cert.Leaf = crt + cert.Certificate = append(cert.Certificate, crt.Raw) + cert.PrivateKey = priv + return cert, nil } -// NewRawCertificate creates and returns a raw certificate for the given host. -// It generates a self-signed TLS certificate and a ED25519 private key. -func NewRawCertificate(host string, duration time.Duration) (crt, key []byte, err error) { - // Generate a ED25519 private key +// newX509KeyPair creates and returns a new certificate and private key. +func newX509KeyPair(host string, duration time.Duration) (*x509.Certificate, crypto.PrivateKey, error) { + // Generate an ED25519 private key _, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { return nil, nil, err } - public := priv.Public().(ed25519.PublicKey) + public := priv.Public() // ED25519 keys should have the DigitalSignature KeyUsage bits set // in the x509.Certificate template keyUsage := x509.KeyUsageDigitalSignature - notBefore := time.Now() - notAfter := notBefore.Add(duration) - - // 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 } + notBefore := time.Now() + notAfter := notBefore.Add(duration) + template := x509.Certificate{ SerialNumber: serialNumber, NotBefore: notBefore, @@ -122,32 +123,13 @@ func NewRawCertificate(host string, duration time.Duration) (crt, key []byte, er } } - // Create the certificate - cert, err := x509.CreateCertificate(rand.Reader, &template, &template, public, priv) + crt, 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{} + cert, err := x509.ParseCertificate(crt) 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 + return cert, priv, nil } diff --git a/examples/cert.go b/examples/cert.go index 7d3597d..4c16a01 100644 --- a/examples/cert.go +++ b/examples/cert.go @@ -3,6 +3,10 @@ package main import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/pem" "log" "os" "time" @@ -13,19 +17,27 @@ import ( func main() { host := "localhost" duration := 365 * 24 * time.Hour - crt, key, err := gmi.NewRawCertificate(host, duration) + cert, err := gmi.NewCertificate(host, duration) if err != nil { log.Fatal(err) } - - if err := writeX509KeyPair(host, crt, key); err != nil { + if err := writeCertificate(host, cert); err != nil { log.Fatal(err) } } -// writeX509KeyPair writes the provided certificate and private key +// writeCertificate writes the provided certificate and private key // to path.crt and path.key respectively. -func writeX509KeyPair(path string, crt, key []byte) error { +func writeCertificate(path string, cert tls.Certificate) error { + crt, err := marshalX509Certificate(cert.Leaf.Raw) + if err != nil { + return err + } + key, err := marshalPrivateKey(cert.PrivateKey) + if err != nil { + return err + } + // Write the certificate crtPath := path + ".crt" crtOut, err := os.OpenFile(crtPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) @@ -47,3 +59,25 @@ func writeX509KeyPair(path string, crt, key []byte) error { } return nil } + +// marshalX509Certificate returns a PEM-encoded version of the given raw certificate. +func marshalX509Certificate(cert []byte) ([]byte, error) { + var b bytes.Buffer + if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil { + return nil, err + } + return b.Bytes(), nil +} + +// marshalPrivateKey returns PEM encoded versions of the given certificate and private key. +func marshalPrivateKey(priv interface{}) ([]byte, error) { + var b bytes.Buffer + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return nil, err + } + if err := pem.Encode(&b, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + return nil, err + } + return b.Bytes(), nil +} diff --git a/examples/server.go b/examples/server.go index 5892e16..c3b1e39 100644 --- a/examples/server.go +++ b/examples/server.go @@ -3,8 +3,12 @@ package main import ( + "bytes" "crypto/tls" + "crypto/x509" + "encoding/pem" "log" + "os" "time" "git.sr.ht/~adnano/gmi" @@ -22,22 +26,18 @@ func main() { case gmi.ErrCertificateExpired: log.Print("Old certificate expired, creating new one") // Generate a new certificate if the old one is expired. - crt, key, err := gmi.NewRawCertificate(hostname, time.Minute) + cert, err := gmi.NewCertificate(hostname, time.Minute) if err != nil { // Failed to generate new certificate, abort return nil } // Store and return the new certificate - err = writeX509KeyPair("/var/lib/gemini/certs/"+hostname, crt, key) + err = writeCertificate("/var/lib/gemini/certs/"+hostname, cert) if err != nil { return nil } - newCert, err := tls.X509KeyPair(crt, key) - if err != nil { - return nil - } - store.Add(hostname, newCert) - return &newCert + store.Add(hostname, cert) + return &cert } } return cert @@ -52,9 +52,18 @@ func main() { } } -// writeX509KeyPair writes the provided certificate and private key +// writeCertificate writes the provided certificate and private key // to path.crt and path.key respectively. -func writeX509KeyPair(path string, crt, key []byte) error { +func writeCertificate(path string, cert tls.Certificate) error { + crt, err := marshalX509Certificate(cert.Leaf.Raw) + if err != nil { + return err + } + key, err := marshalPrivateKey(cert.PrivateKey) + if err != nil { + return err + } + // Write the certificate crtPath := path + ".crt" crtOut, err := os.OpenFile(crtPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) @@ -76,3 +85,25 @@ func writeX509KeyPair(path string, crt, key []byte) error { } return nil } + +// marshalX509Certificate returns a PEM-encoded version of the given raw certificate. +func marshalX509Certificate(cert []byte) ([]byte, error) { + var b bytes.Buffer + if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil { + return nil, err + } + return b.Bytes(), nil +} + +// marshalPrivateKey returns PEM encoded versions of the given certificate and private key. +func marshalPrivateKey(priv interface{}) ([]byte, error) { + var b bytes.Buffer + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return nil, err + } + if err := pem.Encode(&b, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + return nil, err + } + return b.Bytes(), nil +}