aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoradnano <[email protected]>2020-09-27 13:50:48 -0400
committeradnano <[email protected]>2020-09-27 13:50:48 -0400
commit2eb7fb9ba48751cb26efd6324285be3f1f4a98da (patch)
treedbaceef3ff42918971042ba86b4160cf4387ec64
parentAdjust user prompt in client example (diff)
downloadgo-gemini-2eb7fb9ba48751cb26efd6324285be3f1f4a98da.tar.xz
go-gemini-2eb7fb9ba48751cb26efd6324285be3f1f4a98da.zip
Implement certificate creation
-rw-r--r--.gitignore2
-rw-r--r--README.md2
-rw-r--r--cert.go130
-rw-r--r--client.go4
-rw-r--r--examples/cert/cert.go22
-rw-r--r--examples/client/.gitignore2
-rw-r--r--examples/client/client.go2
-rw-r--r--examples/server/.gitignore2
-rw-r--r--examples/server/server.go2
-rw-r--r--store.go24
10 files changed, 159 insertions, 33 deletions
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]
-}